diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..f784c8faf --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ['https://www.heidisql.com/donate.php'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index a2776eff5..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - - - -**Preconditions** - -* HeidiSQL version: -* Database type and version: -* OS: - -**Describe the bug** - - -**To Reproduce** - - -**Screenshots or Crash reports** - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..386650a72 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,36 @@ +name: Bug report +description: Create a report to help us improve +labels: ["bug"] +body: + - type: textarea + attributes: + label: Description + description: "A clear and concise description of what the bug is. Screenshots welcome!" + validations: + required: true + - type: input + id: heidisql_version + attributes: + label: HeidiSQL version and OS + placeholder: "Example: 12.15 Linux GTK2" + validations: + required: true + - type: input + id: database_software + attributes: + label: Database server version + placeholder: "Example: MariaDB 10.3.27, or just '-' if irrelevant" + validations: + required: true + - type: textarea + attributes: + label: Reproduction recipe + placeholder: "Instructions for reproducing the problem" + validations: + required: true + - type: textarea + id: error_log + attributes: + label: Error/Backtrace + placeholder: "Optional, attach or paste crash report here" + render: shell diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..2f52e4bc7 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Supported Versions + +Currently supported releases with security updates: + +| Version | Supported | +| ------- | ------------------ | +| 12.x | :white_check_mark: | +| < 12.x | :x: | + +## Reporting a Vulnerability + +When reporting a vulnerability, please file a ticket here. You may also send an +email to security@heidisql.com . + +It is important that the report is _valid_, and I am able to _understand_ the vulnerability impact. +If so, you may expect an update within weeks, probably quicker. I'll do my best to keep the +software and the user systems intact. diff --git a/build.php b/build.php new file mode 100644 index 000000000..6d85508a3 --- /dev/null +++ b/build.php @@ -0,0 +1,243 @@ +> '.$command); + exec($command.' 2>&1', $output, $resultCode); + if(!$returnOutput) { + foreach ($output as $oline) { + dumpMessage('# ' . ($resultCode ? 'Error: ' : '') . $oline); + } + } + $success = $resultCode == 0; + if(!$success) { + dumpMessage('Last command failed, terminating.'); + exit(1); + } + return $returnOutput ? $output : $success; +} + + +/** + * Return file names from given directory, recursively + * @param string $path file path + * @param string $filepattern file pattern, e.g. "*.xml" + * @return array files + */ +function globRecursive(string $path, string $filepattern): array +{ + static $filecount=0; + $path = rtrim($path, '/\\'); + + // Find files in path + $files = glob($path . DS . $filepattern, GLOB_BRACE); + $filecount += count($files); + + // Find subdirectories in path, and do recursion + $dirs = glob($path . DS . '*', GLOB_ONLYDIR); + foreach($dirs as $d) + { + $files = array_merge($files, globRecursive($d, $filepattern)); + } + + return $files; +} + + +chdir(BASE_DIR); +dumpMessage('Detect Git revision...'); +$gitCommits = execCommand('git log --pretty=oneline', true); +if(empty($gitCommits)) { + die('No commits found.'); +} +$lastCommitRevision = count($gitCommits) + 671; // The number of earlier Subversion commits which I could not migrate to Git +$lastCommitHash = substr($gitCommits[0], 0, strpos($gitCommits[0], ' ')); + +// start the build process +dumpMessage('Compile commit '.$lastCommitHash.' (revision '.$lastCommitRevision.')', true); +chdir(BASE_DIR); + +dumpMessage('Remove unversioned files...'); +execCommand('git clean -dfx'); + +dumpMessage('Download fresh translation files ...', true); +execCommand('extra\\internationalization\\tx.exe pull -a'); + +dumpMessage('Compile .po translation files...'); +$po_files = globRecursive('out\\locale\\', '*.po'); +foreach($po_files as $po_file) +{ + $mo_file = preg_replace('#\.po$#', '.mo', $po_file); + execCommand('"extra\\internationalization\\msgfmt.exe" -o '.$mo_file.' '.$po_file); +} + +$compileBits = ['64']; + +foreach($compileBits as $bit) +{ + dumpMessage('********* Compile '.$bit.' bit executable', true); + chdir(BASE_DIR); + + compileComponent('synedit', 'SynEdit_R.dpk', $bit); + + compileComponent('virtualtreeview', 'VirtualTreesR.dpk', $bit); + + + chdir(BASE_DIR); + $versionFile = realpath('res\\version.rc'); + dumpMessage('Revert version resource file...', true); + execCommand('git checkout '.$versionFile); + dumpMessage('Modify version resource file...'); + $versionOriginal = file_get_contents($versionFile); + $versionRevision = preg_replace('#(FILEVERSION\s+\d+,\d+,\d+,)(\d+)(\b)#i', '${1}'.$lastCommitRevision.'$3', $versionOriginal); + $versionRevision = str_replace('%APPNAME%', APPNAME, $versionRevision); + preg_match('#FILEVERSION\s+(\d+),(\d+),(\d+),(\d+)\b#i', $versionRevision, $matches); + $shortVersion = $matches[1].'.'.$matches[2].'.'.$matches[3].'.'.$matches[4]; + $fullVersion = $shortVersion.' '.$bit.' Bit'; + $versionRevision = str_replace('%APPVER%', $fullVersion, $versionRevision); + file_put_contents($versionFile, $versionRevision); + + dumpMessage('Compile resource files...', true); + execCommand('"'.COMPILER_DIR . 'brcc32.exe" '.$versionFile); + execCommand('"'.COMPILER_DIR . 'cgrc.exe" res\\icon.rc'); + execCommand('"'.COMPILER_DIR . 'brcc32.exe" res\\icon-question.rc'); + execCommand('"'.COMPILER_DIR . 'brcc32.exe" res\\manifest.rc'); + execCommand('"'.COMPILER_DIR . 'brcc32.exe" res\\updater.rc'); + execCommand('"'.COMPILER_DIR . 'cgrc.exe" res\\styles.rc'); + execCommand('"'.COMPILER_DIR . 'brcc32.exe" source\\vcl-styles-utils\\AwesomeFont.rc'); + execCommand('"'.COMPILER_DIR . 'brcc32.exe" source\\vcl-styles-utils\\AwesomeFont_zip.rc'); + + dumpMessage('Compile main project...', true); + chdir(BASE_DIR.'packages\\'.PACKAGE_DIR); + execCommand(compilerCommand($bit, 'exe').' -E"'.BASE_DIR.'out" heidisql.dpr'); + + dumpMessage('Patch executable with .mo files...', true); + // Must be done before madExcept writes a new crc header, otherwise it will complain about a corrupt .exe + // See http://tech.dir.groups.yahoo.com/group/dxgettext/message/3623 + chdir(BASE_DIR); + execCommand('extra\\internationalization\\assemble.exe out\\'.BIN_NAME.'.exe --dxgettext'); + + dumpMessage('Patch executable with exception handler...', true); + chdir(BASE_DIR.'packages\\'.PACKAGE_DIR); + execCommand('"'.MAD_DIR.'madExcept\\Tools\\madExceptPatch.exe" "'.BASE_DIR.'out\\'.BIN_NAME.'.exe" heidisql.mes'); + + chdir(BASE_DIR); + $renameTo = sprintf('out\\%s%d.exe', BIN_NAME, $bit); + dumpMessage('Rename to '.$renameTo.'...', true); + rename('out\\'.BIN_NAME.'.exe', $renameTo); + +} + + +chdir($start_dir); + diff --git a/components/synedit/Packages/Delphi11.1/SynEdit_BCB.cbproj b/components/synedit/Packages/Delphi11.1/SynEdit_BCB.cbproj deleted file mode 100644 index 2de3101d2..000000000 --- a/components/synedit/Packages/Delphi11.1/SynEdit_BCB.cbproj +++ /dev/null @@ -1,1006 +0,0 @@ - - - {6A19C132-8308-437E-BFF4-B2C9FF204C97} - CppPackage - SynEdit_BCB.cpp - Debug - 19.0 - VCL - True - Win32 - 1 - Package - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_1 - true - true - - - true - Cfg_1 - true - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - true - Cfg_2 - true - true - - - SynEdit_BCB - Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) - 1033 - CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= - ..\..\Source\;$(BDS)\include\vcl;$(IncludePath) - rtl.lib;vcldb.lib;adortl.lib;bdertl.lib;vcldbx.lib;ibxpress.lib;dsnap.lib;dsnapcon.lib;teeui.lib;teedb.lib;tee.lib;bcbsmp.lib;inetdbbde.lib;inetdbxpress.lib;dbexpress.lib;bcbie.lib;soaprtl.lib;dbxcds.lib;indycore.lib;indysystem.lib;bcboffice2k.lib - true - .\;$(BDS)\lib;$(BDS)\lib\obj;$(DCC_IncludePath) - true - Windows - .\;$(BDS)\lib;$(BDS)\lib\obj;$(DCC_UnitSearchPath) - ..\..\Source;$(BDS)\include;$(BDS)\include\vcl;$(BRCC_IncludePath) - -I..\..\Source;$(BDS)\include;$(BDS)\include\vcl;$(BDS)\include\dinkumware - true - true - ..\..\Source;$(BDS)\include;$(BDS)\include\vcl;$(TASM_IncludePath) - true - ..\..\Source;$(BDS)\include;$(BDS)\include\vcl;$(BCC_IncludePath) - true - JPHNE - true - true - bpl - . - Package - /w2 - rtl.lib;vcldb.lib;adortl.lib;vcldbx.lib;dsnap.lib;dsnapcon.lib;teeui.lib;teedb.lib;tee.lib;dsnapcon.lib;bcbsmp.lib;inetdbbde.lib;inetdbxpress.lib;dbexpress.lib;bcbie.lib;soaprtl.lib;dbxcds.lib;indycore.lib;indysystem.lib;bcboffice2k.lib - SynEdit component suite - ..\..\Source\;$(BDS)\include\vcl;$(BDS)\lib\obj;$(BDS)\lib;$(BDS)\Source\Toolsapi;$(BDS)\lib\psdk;$(ILINK_LibraryPath) - true - -M -LUDesignIDE - true - true - false - - - -Vx -r- -k -Ve - _DEBUG;$(TASM_Defines) - _DEBUG;$(BRCC_Defines) - $(BDSINCLUDE)\windows\vcl;$(IncludePath) - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - true - 1033 - - - _DEBUG;$(TASM_Defines) - _DEBUG;$(BRCC_Defines) - $(BDSINCLUDE)\windows\vcl;$(IncludePath) - - - $(BDS)\lib\debug;$(ILINK_LibraryPath);$(ILINK_LibraryPath) - Debug_Build - DEBUG;$(DCC_Define);$(DCC_Define) - true - Full - -M -V -LUDesignIDE - true - true - - - _DEBUG;$(BCC_Defines);$(BCC_Defines) - -Vx -r- -k -Ve -k - rtl.lib;vcldb.lib;adortl.lib;vcldbx.lib;dsnap.lib;dsnapcon.lib;teeui.lib;teedb.lib;tee.lib;bcbsmp.lib;inetdbbde.lib;inetdbxpress.lib;dbexpress.lib;bcbie.lib;soaprtl.lib;dbxcds.lib;indycore.lib;indysystem.lib;bcboffice2k.lib - ..\..\Source;$(DCC_UnitSearchPath) - true - 110A - - - _DEBUG;$(BCC_Defines);$(BCC_Defines) - - - true - Release_Build - -M -$O+ -LUDesignIDE - None - $(BDS)\lib\release;$(ILINK_LibraryPath);$(ILINK_LibraryPath) - true - - - NDEBUG;$(BCC_Defines);$(BCC_Defines) - -Vx -r- -k -Ve -r - - - NDEBUG;$(BCC_Defines);$(BCC_Defines) - - - - 38 - 3 - - - 14 - 11 - - - 29 - 0 - - - 8 - 5 - - - 9 - 2 - - - 8 - 7 - - - 17 - 10 - - - 12 - 11 - - - -1 - 23 - - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - - - - - CPlusPlusBuilder.Personality.12 - CppPackage - - - - False - False - 1 - 0 - 0 - 0 - False - False - False - False - False - 1033 - 1252 - - - - - 1.0.0.0 - - - - - - 1.0.0.0 - - - - 1 - ..\..\Source;$(BCB)\include;$(BCB)\include\vcl - - - 2 - ..\..\Source;$(BCB)\lib\obj;$(BCB)\lib;$(BCB)\Source\Toolsapi - ..\..\Source;$(BCB)\lib\obj;$(BCB)\lib - - - 1 - $(BCB)\source\vcl - - - 1 - _DEBUG - - - 4 - C:\Arquivos de programas\Borland\CBuilder6\Lib\ - C:\Arquivos de programas\Borland\CBuilder6\Lib - $(BCB)\lib\ - $(BCB)\lib - - - $(BCB)\source\vcl - - - - - False - - - - - - - False - - False - - True - False - - - 0 - 0 - 0 - - - 1 - 1 - 1 - - - False - True - True - False - - - SynEdit_BCB.cpp - - - Embarcadero C++Builder Office 2000 Servers Package - Embarcadero C++Builder Office XP Servers Package - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - - - - True - False - - - - - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - SynEdit_BCB.bpl - true - - - - - true - - - - - true - - - - - SynEdit_BCB.tds - true - - - - - true - - - - - 1 - - - 0 - - - - - classes - 1 - - - classes - 1 - - - - - res\xml - 1 - - - res\xml - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - library\lib\armeabi - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\mips - 1 - - - library\lib\mips - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\values-v21 - 1 - - - res\values-v21 - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-small - 1 - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - res\drawable-xlarge - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - 1 - - - 1 - - - 0 - - - - - 1 - .framework - - - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - - - 12 - - - - diff --git a/components/synedit/Packages/Delphi11.1/SynEdit_D.dproj b/components/synedit/Packages/Delphi11.1/SynEdit_D.dproj deleted file mode 100644 index 3be6e06e8..000000000 --- a/components/synedit/Packages/Delphi11.1/SynEdit_D.dproj +++ /dev/null @@ -1,155 +0,0 @@ - - - {6E21797B-4D50-4028-9D52-05CDFE6CEAEA} - SynEdit_D.dpk - True - Debug - 1 - Package - None - 19.4 - Win32 - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - SynEdit_D - ..\..\build\$(Platform) - true - CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= - true - 00400000 - true - SynEdit component suite - System;Xml;Data;Datasnap;Web;Soap;Vcl;Winapi;$(DCC_Namespace) - false - false - true - false - 1031 - false - false - - - SynEdit_R;$(DCC_UsePackage) - System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - true - 1033 - - - System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) - Debug - true - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - 1033 - - - false - 0 - 0 - RELEASE;$(DCC_Define) - - - DEBUG;$(DCC_Define) - false - true - - - true - 1033 - - - - MainSource - - - - - - - - - Base - - - Cfg_1 - Base - - - Cfg_2 - Base - - - - Delphi.Personality.12 - Package - - - - SynEdit_D.dpk - - - True - False - 1 - 0 - 0 - 0 - False - False - False - False - False - 1031 - 1252 - - - - - 1.0.0.0 - - - - - - 1.0.0.0 - - - - - True - False - - - 12 - - - - diff --git a/components/synedit/Packages/Delphi11.1/SynEdit_R.dpk b/components/synedit/Packages/Delphi11.1/SynEdit_R.dpk deleted file mode 100644 index 7243e342b..000000000 --- a/components/synedit/Packages/Delphi11.1/SynEdit_R.dpk +++ /dev/null @@ -1,150 +0,0 @@ -package SynEdit_R; - -{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} -{$ALIGN 8} -{$ASSERTIONS ON} -{$BOOLEVAL OFF} -{$DEBUGINFO OFF} -{$EXTENDEDSYNTAX ON} -{$IMPORTEDDATA ON} -{$IOCHECKS ON} -{$LOCALSYMBOLS ON} -{$LONGSTRINGS ON} -{$OPENSTRINGS ON} -{$OPTIMIZATION OFF} -{$OVERFLOWCHECKS OFF} -{$RANGECHECKS OFF} -{$REFERENCEINFO ON} -{$SAFEDIVIDE OFF} -{$STACKFRAMES ON} -{$TYPEDADDRESS OFF} -{$VARSTRINGCHECKS ON} -{$WRITEABLECONST ON} -{$MINENUMSIZE 1} -{$IMAGEBASE $400000} -{$DEFINE DEBUG} -{$ENDIF IMPLICITBUILDING} -{$DESCRIPTION 'SynEdit component suite runtime'} -{$RUNONLY} -{$IMPLICITBUILD OFF} - -requires - vcl, - vcldb, - vclx, - rtl; - -contains - SynTextDrawer in '..\..\Source\SynTextDrawer.pas', - SynAutoCorrect in '..\..\Source\SynAutoCorrect.pas', - SynAutoCorrectEditor in '..\..\Source\SynAutoCorrectEditor.pas' {frmAutoCorrectEditor}, - SynCompletionProposal in '..\..\Source\SynCompletionProposal.pas', - SynDBEdit in '..\..\Source\SynDBEdit.pas', - SynEdit in '..\..\Source\SynEdit.pas', - SynEditAutoComplete in '..\..\Source\SynEditAutoComplete.pas', - SynEditCodeFolding in '..\..\Source\SynEditCodeFolding.pas', - SynEditDocumentManager in '..\..\Source\SynEditDocumentManager.pas', - SynEditExport in '..\..\Source\SynEditExport.pas', - SynEditHighlighter in '..\..\Source\SynEditHighlighter.pas', - SynEditHighlighterOptions in '..\..\Source\SynEditHighlighterOptions.pas', - SynEditKbdHandler in '..\..\Source\SynEditKbdHandler.pas', - SynEditKeyCmdEditor in '..\..\Source\SynEditKeyCmdEditor.pas' {SynEditKeystrokeEditorForm}, - SynEditKeyCmds in '..\..\Source\SynEditKeyCmds.pas', - SynEditKeyCmdsEditor in '..\..\Source\SynEditKeyCmdsEditor.pas' {SynEditKeystrokesEditorForm}, - SynEditKeyConst in '..\..\Source\SynEditKeyConst.pas', - SynEditMiscClasses in '..\..\Source\SynEditMiscClasses.pas', - SynEditMiscProcs in '..\..\Source\SynEditMiscProcs.pas', - SynEditOptionsDialog in '..\..\Source\SynEditOptionsDialog.pas' {fmEditorOptionsDialog}, - SynEditPlugins in '..\..\Source\SynEditPlugins.pas', - SynEditPrint in '..\..\Source\SynEditPrint.pas', - SynEditPrinterInfo in '..\..\Source\SynEditPrinterInfo.pas', - SynEditPrintHeaderFooter in '..\..\Source\SynEditPrintHeaderFooter.pas', - SynEditPrintMargins in '..\..\Source\SynEditPrintMargins.pas', - SynEditPrintMarginsDialog in '..\..\Source\SynEditPrintMarginsDialog.pas' {SynEditPrintMarginsDlg}, - SynEditPrintPreview in '..\..\Source\SynEditPrintPreview.pas', - SynEditPrintTypes in '..\..\Source\SynEditPrintTypes.pas', - SynEditPythonBehaviour in '..\..\Source\SynEditPythonBehaviour.pas', - SynEditRegexSearch in '..\..\Source\SynEditRegexSearch.pas', - SynEditSearch in '..\..\Source\SynEditSearch.pas', - SynEditStrConst in '..\..\Source\SynEditStrConst.pas', - SynEditTextBuffer in '..\..\Source\SynEditTextBuffer.pas', - SynEditTypes in '..\..\Source\SynEditTypes.pas', - SynEditWordWrap in '..\..\Source\SynEditWordWrap.pas', - SynExportHTML in '..\..\Source\SynExportHTML.pas', - SynExportRTF in '..\..\Source\SynExportRTF.pas', - SynExportTeX in '..\..\Source\SynExportTeX.pas', - SynHighlighterADSP21xx in '..\..\Source\SynHighlighterADSP21xx.pas', - SynHighlighterAsm in '..\..\Source\SynHighlighterAsm.pas', - SynHighlighterAsmMASM in '..\..\Source\SynHighlighterAsmMASM.pas', - SynHighlighterAWK in '..\..\Source\SynHighlighterAWK.pas', - SynHighlighterBaan in '..\..\Source\SynHighlighterBaan.pas', - SynHighlighterBat in '..\..\Source\SynHighlighterBat.pas', - SynHighlighterCAC in '..\..\Source\SynHighlighterCAC.pas', - SynHighlighterCache in '..\..\Source\SynHighlighterCache.pas', - SynHighlighterCobol in '..\..\Source\SynHighlighterCobol.pas', - SynHighlighterCPM in '..\..\Source\SynHighlighterCPM.pas', - SynHighlighterCpp in '..\..\Source\SynHighlighterCpp.pas', - SynHighlighterCS in '..\..\Source\SynHighlighterCS.pas', - SynHighlighterCss in '..\..\Source\SynHighlighterCss.pas', - SynHighlighterDfm in '..\..\Source\SynHighlighterDfm.pas', - SynHighlighterDml in '..\..\Source\SynHighlighterDml.pas', - SynHighlighterDOT in '..\..\Source\SynHighlighterDOT.pas', - SynHighlighterDWS in '..\..\Source\SynHighlighterDWS.pas', - SynHighlighterECMAScript in '..\..\Source\SynHighlighterECMAScript.pas', - SynHighlighterEiffel in '..\..\Source\SynHighlighterEiffel.pas', - SynHighlighterFortran in '..\..\Source\SynHighlighterFortran.pas', - SynHighlighterFoxpro in '..\..\Source\SynHighlighterFoxpro.pas', - SynHighlighterGalaxy in '..\..\Source\SynHighlighterGalaxy.pas', - SynHighlighterGeneral in '..\..\Source\SynHighlighterGeneral.pas', - SynHighlighterGLSL in '..\..\Source\SynHighlighterGLSL.pas', - SynHighlighterGo in '..\..\Source\SynHighlighterGo.pas', - SynHighlighterGWS in '..\..\Source\SynHighlighterGWS.pas', - SynHighlighterHashEntries in '..\..\Source\SynHighlighterHashEntries.pas', - SynHighlighterHaskell in '..\..\Source\SynHighlighterHaskell.pas', - SynHighlighterHC11 in '..\..\Source\SynHighlighterHC11.pas', - SynHighlighterHP48 in '..\..\Source\SynHighlighterHP48.pas', - SynHighlighterHtml in '..\..\Source\SynHighlighterHtml.pas', - SynHighlighterIDL in '..\..\Source\SynHighlighterIDL.pas', - SynHighlighterIni in '..\..\Source\SynHighlighterIni.pas', - SynHighlighterInno in '..\..\Source\SynHighlighterInno.pas', - SynHighlighterJava in '..\..\Source\SynHighlighterJava.pas', - SynHighlighterJScript in '..\..\Source\SynHighlighterJScript.pas', - SynHighlighterJSON in '..\..\Source\SynHighlighterJSON.pas', - SynHighlighterKix in '..\..\Source\SynHighlighterKix.pas', - SynHighlighterLDraw in '..\..\Source\SynHighlighterLDraw.pas', - SynHighlighterLLVM in '..\..\Source\SynHighlighterLLVM.pas', - SynHighlighterM3 in '..\..\Source\SynHighlighterM3.pas', - SynHighlighterModelica in '..\..\Source\SynHighlighterModelica.pas', - SynHighlighterMsg in '..\..\Source\SynHighlighterMsg.pas', - SynHighlighterMulti in '..\..\Source\SynHighlighterMulti.pas', - SynHighlighterPas in '..\..\Source\SynHighlighterPas.pas', - SynHighlighterPerl in '..\..\Source\SynHighlighterPerl.pas', - SynHighlighterPHP in '..\..\Source\SynHighlighterPHP.pas', - SynHighlighterProgress in '..\..\Source\SynHighlighterProgress.pas', - SynHighlighterPython in '..\..\Source\SynHighlighterPython.pas', - SynHighlighterRC in '..\..\Source\SynHighlighterRC.pas', - SynHighlighterRexx in '..\..\Source\SynHighlighterRexx.pas', - SynHighlighterRuby in '..\..\Source\SynHighlighterRuby.pas', - SynHighlighterSDD in '..\..\Source\SynHighlighterSDD.pas', - SynHighlighterSml in '..\..\Source\SynHighlighterSml.pas', - SynHighlighterSQL in '..\..\Source\SynHighlighterSQL.pas', - SynHighlighterST in '..\..\Source\SynHighlighterST.pas', - SynHighlighterTclTk in '..\..\Source\SynHighlighterTclTk.pas', - SynHighlighterTeX in '..\..\Source\SynHighlighterTeX.pas', - SynHighlighterUNIXShellScript in '..\..\Source\SynHighlighterUNIXShellScript.pas', - SynHighlighterUnreal in '..\..\Source\SynHighlighterUnreal.pas', - SynHighlighterURI in '..\..\Source\SynHighlighterURI.pas', - SynHighlighterVB in '..\..\Source\SynHighlighterVB.pas', - SynHighlighterVBScript in '..\..\Source\SynHighlighterVBScript.pas', - SynHighlighterVrml97 in '..\..\Source\SynHighlighterVrml97.pas', - SynHighlighterWebIDL in '..\..\Source\SynHighlighterWebIDL.pas', - SynHighlighterXML in '..\..\Source\SynHighlighterXML.pas', - SynHighlighterZPL in '..\..\Source\SynHighlighterZPL.pas', - SynMacroRecorder in '..\..\Source\SynMacroRecorder.pas', - SynMemo in '..\..\Source\SynMemo.pas', - SynRegExpr in '..\..\Source\SynRegExpr.pas', - SynURIOpener in '..\..\Source\SynURIOpener.pas', - SynUnicode in '..\..\Source\SynUnicode.pas' {$IFNDEF CPUX64}, - SynUsp10 in '..\..\Source\SynUsp10.pas' {$ENDIF}; - -end. \ No newline at end of file diff --git a/components/synedit/Packages/Delphi11.2/SynEdit.groupproj b/components/synedit/Packages/Delphi11.2/SynEdit.groupproj deleted file mode 100644 index 85a0b3f56..000000000 --- a/components/synedit/Packages/Delphi11.2/SynEdit.groupproj +++ /dev/null @@ -1,48 +0,0 @@ - - - {D7950D4A-962A-4E8F-982E-E7E052259FA8} - - - - - - - - - - - Default.Personality.12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/components/synedit/Packages/Delphi11.2/SynEdit_D.dpk b/components/synedit/Packages/Delphi11.2/SynEdit_D.dpk deleted file mode 100644 index 33b35f7b6..000000000 --- a/components/synedit/Packages/Delphi11.2/SynEdit_D.dpk +++ /dev/null @@ -1,41 +0,0 @@ -package SynEdit_D; - -{$R '..\..\Source\SynEditReg.dcr'} -{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} -{$ALIGN 8} -{$ASSERTIONS ON} -{$BOOLEVAL OFF} -{$DEBUGINFO ON} -{$EXTENDEDSYNTAX ON} -{$IMPORTEDDATA ON} -{$IOCHECKS ON} -{$LOCALSYMBOLS ON} -{$LONGSTRINGS ON} -{$OPENSTRINGS ON} -{$OPTIMIZATION OFF} -{$OVERFLOWCHECKS OFF} -{$RANGECHECKS OFF} -{$REFERENCEINFO ON} -{$SAFEDIVIDE OFF} -{$STACKFRAMES ON} -{$TYPEDADDRESS OFF} -{$VARSTRINGCHECKS ON} -{$WRITEABLECONST ON} -{$MINENUMSIZE 1} -{$IMAGEBASE $400000} -{$DEFINE DEBUG} -{$ENDIF IMPLICITBUILDING} -{$DESCRIPTION 'SynEdit component suite'} -{$DESIGNONLY} -{$IMPLICITBUILD ON} - -requires - designide, - SynEdit_R; - -contains - SynEditReg in '..\..\Source\SynEditReg.pas', - SynEditPropertyReg in '..\..\Source\SynEditPropertyReg.pas', - SynHighlighterManager in '..\..\Source\SynHighlighterManager.pas'; - -end. diff --git a/components/synedit/Packages/Delphi11.2/SynEdit_R.dproj b/components/synedit/Packages/Delphi11.2/SynEdit_R.dproj deleted file mode 100644 index 842a00c89..000000000 --- a/components/synedit/Packages/Delphi11.2/SynEdit_R.dproj +++ /dev/null @@ -1,1009 +0,0 @@ - - - {AC917C2B-5870-48AD-981D-668AD3E4A533} - SynEdit_R.dpk - True - Debug - 3 - Package - VCL - 19.5 - Win32 - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - SynEdit_R - ..\..\build\$(Platform) - true - true - CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= - 00400000 - true - Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;Winapi;System.Win;Bde;$(DCC_Namespace) - SynEdit component suite runtime - true - false - false - true - false - 1031 - false - false - - - Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) - true - 1033 - - - Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) - 1033 - true - - - false - 0 - 0 - RELEASE;$(DCC_Define) - - - DEBUG;$(DCC_Define) - false - true - - - true - 1033 - false - - - - MainSource - - - - - - - - -
frmAutoCorrectEditor
-
- - - - - - - - - - - -
SynEditKeystrokeEditorForm
-
- - -
SynEditKeystrokesEditorForm
-
- - - - -
fmEditorOptionsDialog
-
- - - - - - -
SynEditPrintMarginsDlg
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$IFNDEF CPUX64
-
- -
$ENDIF
-
- - Base - - - Cfg_1 - Base - - - Cfg_2 - Base - -
- - Delphi.Personality.12 - Package - - - - SynEdit_R.dpk - - - True - False - 1 - 0 - 0 - 0 - False - False - False - False - False - 1031 - 1252 - - - - - 1.0.0.0 - - - - - - 1.0.0.0 - - - - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - - - - True - True - - - - - - 1 - - - 0 - - - - - classes - 64 - - - classes - 64 - - - - - res\xml - 1 - - - res\xml - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - library\lib\armeabi - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\mips - 1 - - - library\lib\mips - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\values-v21 - 1 - - - res\values-v21 - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-small - 1 - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - res\drawable-xlarge - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - 1 - - - 1 - - - 0 - - - - - 1 - .framework - - - 1 - .framework - - - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - - - - - 12 - - - - -
diff --git a/components/synedit/Packages/Delphi11.1/SynEdit.groupproj b/components/synedit/Packages/Delphi12.3/SynEdit.groupproj similarity index 100% rename from components/synedit/Packages/Delphi11.1/SynEdit.groupproj rename to components/synedit/Packages/Delphi12.3/SynEdit.groupproj diff --git a/components/synedit/Packages/Delphi11.1/SynEdit_D.dpk b/components/synedit/Packages/Delphi12.3/SynEdit_D.dpk similarity index 100% rename from components/synedit/Packages/Delphi11.1/SynEdit_D.dpk rename to components/synedit/Packages/Delphi12.3/SynEdit_D.dpk diff --git a/components/synedit/Packages/Delphi11.2/SynEdit_D.dproj b/components/synedit/Packages/Delphi12.3/SynEdit_D.dproj similarity index 88% rename from components/synedit/Packages/Delphi11.2/SynEdit_D.dproj rename to components/synedit/Packages/Delphi12.3/SynEdit_D.dproj index 6ff5ce08c..055e92a4b 100644 --- a/components/synedit/Packages/Delphi11.2/SynEdit_D.dproj +++ b/components/synedit/Packages/Delphi12.3/SynEdit_D.dproj @@ -7,8 +7,9 @@ 1 Package None - 19.5 + 20.3 Win32 + SynEdit_D true @@ -23,6 +24,11 @@ Base true + + true + Base + true + true Base @@ -70,6 +76,13 @@ CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 1033 + + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + false 0 @@ -146,6 +159,7 @@ True False + False 12 diff --git a/components/synedit/Packages/Delphi11.2/SynEdit_R.dpk b/components/synedit/Packages/Delphi12.3/SynEdit_R.dpk similarity index 100% rename from components/synedit/Packages/Delphi11.2/SynEdit_R.dpk rename to components/synedit/Packages/Delphi12.3/SynEdit_R.dpk diff --git a/components/synedit/Packages/Delphi11.1/SynEdit_R.dproj b/components/synedit/Packages/Delphi12.3/SynEdit_R.dproj similarity index 86% rename from components/synedit/Packages/Delphi11.1/SynEdit_R.dproj rename to components/synedit/Packages/Delphi12.3/SynEdit_R.dproj index 4a99562bc..fb7620b38 100644 --- a/components/synedit/Packages/Delphi11.1/SynEdit_R.dproj +++ b/components/synedit/Packages/Delphi12.3/SynEdit_R.dproj @@ -7,8 +7,9 @@ 3 Package VCL - 19.4 + 20.1 Win32 + SynEdit_R true @@ -23,6 +24,11 @@ Base true + + true + Base + true + true Base @@ -68,6 +74,13 @@ 1033 true + + Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + false 0 @@ -82,6 +95,7 @@ true 1033 + false @@ -263,18 +277,19 @@ 1.0.0.0 + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + True True + False - - - - SynEdit_R.bpl - true - - + + + 1 @@ -283,26 +298,6 @@ 0 - - - classes - 64 - - - classes - 64 - - - - - classes - 1 - - - classes - 1 - - res\xml @@ -313,12 +308,6 @@ 1 - - - library\lib\armeabi-v7a - 1 - - library\lib\armeabi @@ -371,6 +360,16 @@ 1 + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + res\values @@ -391,6 +390,76 @@ 1 + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\values-v35 + 1 + + + res\values-v35 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + res\values @@ -401,6 +470,16 @@ 1 + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + res\drawable @@ -571,6 +650,56 @@ 1 + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + 1 @@ -626,7 +755,7 @@ 1 .dylib - + 1 .dylib @@ -660,7 +789,7 @@ 0 - + 0 @@ -676,196 +805,247 @@ 0 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + 1 + + 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + + + + Contents\Resources 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Contents\Resources + 1 + + + Contents\Resources 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + library\lib\armeabi-v7a 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + library\lib\arm64-v8a 1 - - 1 1 - + 1 - - - + 1 - + 1 - + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a 1 - - + + 1 - + 1 - + 1 - + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1 - + + + + 1 1 - + 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + + Assets 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + Assets 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + Assets 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Assets 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 @@ -874,7 +1054,7 @@ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 @@ -884,7 +1064,7 @@ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 @@ -894,7 +1074,7 @@ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 @@ -904,7 +1084,7 @@ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 @@ -914,7 +1094,7 @@ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 @@ -924,7 +1104,7 @@ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 @@ -934,7 +1114,7 @@ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 @@ -944,133 +1124,16 @@ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - + @@ -1078,6 +1141,7 @@ + 12 diff --git a/components/synedit/Source/SynCompletionProposal.pas b/components/synedit/Source/SynCompletionProposal.pas index 1fa39cd58..9c5b7da39 100644 --- a/components/synedit/Source/SynCompletionProposal.pas +++ b/components/synedit/Source/SynCompletionProposal.pas @@ -2722,9 +2722,14 @@ function TSynBaseCompletionProposal.GetDefaultKind: SynCompletionType; procedure TSynBaseCompletionProposal.SetDefaultKind(const Value: SynCompletionType); begin - Form.DefaultType := Value; - Form.DisplayType := Value; - Form.RecreateWnd; + if Form.DefaultType <> Value then begin + Form.DefaultType := Value; + Form.DisplayType := Value; + if Form.HandleAllocated then + Form.RecreateWnd; + end + else + Form.DisplayType := Value; end; procedure TSynBaseCompletionProposal.SetEndOfTokenChar( diff --git a/components/synedit/Source/SynEdit.inc b/components/synedit/Source/SynEdit.inc index 1ae023ac9..bd2ba75a9 100644 --- a/components/synedit/Source/SynEdit.inc +++ b/components/synedit/Source/SynEdit.inc @@ -120,6 +120,10 @@ (* SYN_DELPHI_10_2_UP : Delphi RX 10.2 or higher is being used. *) (* SYN_DELPHI_10_3 : Delphi RX 10.3 (Rio) is being used. *) (* SYN_DELPHI_10_3_UP : Delphi RX 10.3 or higher is being used. *) +(* SYN_DELPHI_10_4 : Delphi RX 10.4 (Sydney) is being used. *) +(* SYN_DELPHI_10_4_UP : Delphi RX 10.4 or higher is being used. *) +(* SYN_DELPHI_11 : Delphi RX 11 (Alexandrai) is being used. *) +(* SYN_DELPHI_11_UP : Delphi RX 11 or higher is being used. *) (* SYN_KYLIX : Kylix 1.0 is being using. *) (******************************************************************************) @@ -161,10 +165,16 @@ { VERXXX to SYN_COMPILERX, SYN_DELPHIX and SYN_CPPBX mappings } {------------------------------------------------------------------------------} +{$IFDEF VER360} + {$DEFINE SYN_COMPILER_29} + {$DEFINE SYN_DELPHI} + {$DEFINE SYN_DELPHI_12} +{$ENDIF} + {$IFDEF VER350} {$DEFINE SYN_COMPILER_28} {$DEFINE SYN_DELPHI} - {$DEFINE SYN_DELPHI_11_0} + {$DEFINE SYN_DELPHI_11} {$ENDIF} {$IFDEF VER340} @@ -805,6 +815,37 @@ {$DEFINE SYN_COMPILER_28_UP} {$ENDIF} +{$IFDEF SYN_COMPILER_29} + {$DEFINE SYN_COMPILER_1_UP} + {$DEFINE SYN_COMPILER_2_UP} + {$DEFINE SYN_COMPILER_3_UP} + {$DEFINE SYN_COMPILER_4_UP} + {$DEFINE SYN_COMPILER_5_UP} + {$DEFINE SYN_COMPILER_6_UP} + {$DEFINE SYN_COMPILER_7_UP} + {$DEFINE SYN_COMPILER_8_UP} + {$DEFINE SYN_COMPILER_9_UP} + {$DEFINE SYN_COMPILER_10_UP} + {$DEFINE SYN_COMPILER_11_UP} + {$DEFINE SYN_COMPILER_12_UP} + {$DEFINE SYN_COMPILER_14_UP} + {$DEFINE SYN_COMPILER_15_UP} + {$DEFINE SYN_COMPILER_16_UP} + {$DEFINE SYN_COMPILER_17_UP} + {$DEFINE SYN_COMPILER_18_UP} + {$DEFINE SYN_COMPILER_19_UP} + {$DEFINE SYN_COMPILER_20_UP} + {$DEFINE SYN_COMPILER_21_UP} + {$DEFINE SYN_COMPILER_22_UP} + {$DEFINE SYN_COMPILER_23_UP} + {$DEFINE SYN_COMPILER_24_UP} + {$DEFINE SYN_COMPILER_25_UP} + {$DEFINE SYN_COMPILER_26_UP} + {$DEFINE SYN_COMPILER_27_UP} + {$DEFINE SYN_COMPILER_28_UP} + {$DEFINE SYN_COMPILER_29_UP} +{$ENDIF} + {$IFDEF SYN_DELPHI_2} {$DEFINE SYN_DELPHI_2_UP} @@ -1202,7 +1243,35 @@ {$DEFINE SYN_DELPHI_10_4_UP} {$ENDIF} -{$IFDEF SYN_DELPHI_11_0} +{$IFDEF SYN_DELPHI_11} + {$DEFINE SYN_DELPHI_2_UP} + {$DEFINE SYN_DELPHI_3_UP} + {$DEFINE SYN_DELPHI_4_UP} + {$DEFINE SYN_DELPHI_5_UP} + {$DEFINE SYN_DELPHI_6_UP} + {$DEFINE SYN_DELPHI_7_UP} + {$DEFINE SYN_DELPHI_8_UP} + {$DEFINE SYN_DELPHI_2005_UP} + {$DEFINE SYN_DELPHI_2006_UP} + {$DEFINE SYN_DELPHI_2007_UP} + {$DEFINE SYN_DELPHI_2009_UP} + {$DEFINE SYN_DELPHI_2010_UP} + {$DEFINE SYN_DELPHI_XE_UP} + {$DEFINE SYN_DELPHI_XE2_UP} + {$DEFINE SYN_DELPHI_XE3_UP} + {$DEFINE SYN_DELPHI_XE4_UP} + {$DEFINE SYN_DELPHI_XE5_UP} + {$DEFINE SYN_DELPHI_XE6_UP} + {$DEFINE SYN_DELPHI_XE7_UP} + {$DEFINE SYN_DELPHI_XE8_UP} + {$DEFINE SYN_DELPHI_10_1_UP} + {$DEFINE SYN_DELPHI_10_2_UP} + {$DEFINE SYN_DELPHI_10_3_UP} + {$DEFINE SYN_DELPHI_10_4_UP} + {$DEFINE SYN_DELPHI_11_UP} +{$ENDIF} + +{$IFDEF SYN_DELPHI_12} {$DEFINE SYN_DELPHI_2_UP} {$DEFINE SYN_DELPHI_3_UP} {$DEFINE SYN_DELPHI_4_UP} @@ -1227,7 +1296,8 @@ {$DEFINE SYN_DELPHI_10_2_UP} {$DEFINE SYN_DELPHI_10_3_UP} {$DEFINE SYN_DELPHI_10_4_UP} - {$DEFINE SYN_DELPHI_11_0_UP} + {$DEFINE SYN_DELPHI_11_UP} + {$DEFINE SYN_DELPHI_12_UP} {$ENDIF} {$IFDEF SYN_CPPB_6} diff --git a/components/synedit/Source/SynHighlighterSQL.pas b/components/synedit/Source/SynHighlighterSQL.pas index d9b7aacdf..77502d12c 100644 --- a/components/synedit/Source/SynHighlighterSQL.pas +++ b/components/synedit/Source/SynHighlighterSQL.pas @@ -934,7 +934,7 @@ implementation 'ALGORITHM,ALL,ALTER,ALWAYS,ANALYSE,ANALYZE,AND,ANY,ARRAY,AS,ASC,' + 'ASENSITIVE,AT,ATTRIBUTE,AUTHENTICATION,AUTOEXTEND_SIZE,AUTO_INCREMENT,' + 'AVG_ROW_LENGTH,BACKUP,BEFORE,BEGIN,BETWEEN,BINLOG,BIT,BLOCK,BOTH,BUCKETS,' + - 'BY,CACHE,CALL,CASCADE,CASCADED,CATALOG_NAME,CHAIN,CHALLENGE_RESPONSE,' + + 'BULK,BY,CACHE,CALL,CASCADE,CASCADED,CATALOG_NAME,CHAIN,CHALLENGE_RESPONSE,' + 'CHANGE,CHANGED,CHANNEL,CHARACTER,CHARSET,CHECK,CHECKSUM,CIPHER,' + 'CLASS_ORIGIN,CLIENT,CLONE,CODE,COLLATE,COLLATION,COLUMN,COLUMNS,' + 'COLUMN_FORMAT,COLUMN_NAME,COMMENT,COMMIT,COMMITTED,COMPLETION,COMPONENT,' + @@ -952,53 +952,53 @@ implementation 'EXTENDED,EXTENT_SIZE,FACTOR,FAILED_LOGIN_ATTEMPTS,FALSE,FAST,FAULTS,' + 'FIELDS,FILE,FILE_BLOCK_SIZE,FILTER,FINISH,FIRST,FIRST_VALUE,FLOAT4,FLOAT8,' + 'FLUSH,FOLLOWING,FOLLOWS,FOR,FORCE,FOREIGN,FOUND,FROM,FULL,FULLTEXT,' + - 'FUNCTION,GENERAL,GENERATED,GEOMCOLLECTION,GET,GET_MASTER_PUBLIC_KEY,' + - 'GET_SOURCE_PUBLIC_KEY,GLOBAL,GRANT,GRANTS,GROUP,GROUPING,GROUPS,' + - 'GROUP_REPLICATION,GTID_ONLY,HAVING,HELP,HIGH_PRIORITY,HISTOGRAM,HISTORY,' + - 'HOST,HOSTS,HOUR_MICROSECOND,HOUR_MINUTE,HOUR_SECOND,IDENTIFIED,IGNORE,' + - 'IGNORE_SERVER_IDS,IMPORT,IN,INACTIVE,INDEX,INDEXES,INFILE,INITIAL,' + - 'INITIAL_SIZE,INITIATE,INNER,INOUT,INSENSITIVE,INSERT,INSERT_METHOD,' + - 'INSTALL,INSTANCE,INT1,INT2,INT3,INT4,INT8,INTO,INVISIBLE,INVOKER,IO,' + - 'IO_AFTER_GTIDS,IO_BEFORE_GTIDS,IO_THREAD,IPC,IS,ISOLATION,ISSUER,JOIN,' + - 'JSON,JSON_TABLE,JSON_VALUE,KEY,KEYRING,KEYS,KEY_BLOCK_SIZE,KILL,LAG,' + - 'LANGUAGE,LAST,LAST_VALUE,LATERAL,LEAD,LEADING,LEAVES,LESS,LEVEL,LIKE,' + - 'LIMIT,LINEAR,LINES,LIST,LOAD,LOCAL,LOCK,LOCKED,LOCKS,LOGFILE,LOGS,LONG,' + - 'LOW_PRIORITY,MASTER,MASTER_AUTO_POSITION,MASTER_BIND,' + - 'MASTER_COMPRESSION_ALGORITHMS,MASTER_CONNECT_RETRY,MASTER_DELAY,' + - 'MASTER_HEARTBEAT_PERIOD,MASTER_HOST,MASTER_LOG_FILE,MASTER_LOG_POS,' + - 'MASTER_PASSWORD,MASTER_PORT,MASTER_PUBLIC_KEY_PATH,MASTER_RETRY_COUNT,' + - 'MASTER_SERVER_ID,MASTER_SSL,MASTER_SSL_CA,MASTER_SSL_CAPATH,' + - 'MASTER_SSL_CERT,MASTER_SSL_CIPHER,MASTER_SSL_CRL,MASTER_SSL_CRLPATH,' + - 'MASTER_SSL_KEY,MASTER_SSL_VERIFY_SERVER_CERT,MASTER_TLS_CIPHERSUITES,' + - 'MASTER_TLS_VERSION,MASTER_USER,MASTER_ZSTD_COMPRESSION_LEVEL,MATCH,' + - 'MAXVALUE,MAX_CONNECTIONS_PER_HOUR,MAX_QUERIES_PER_HOUR,MAX_ROWS,MAX_SIZE,' + - 'MAX_UPDATES_PER_HOUR,MAX_USER_CONNECTIONS,MEDIUM,MEMBER,MESSAGE_TEXT,' + - 'MIDDLEINT,MIGRATE,MINUTE_MICROSECOND,MINUTE_SECOND,MIN_ROWS,MOD,MODE,' + - 'MODIFIES,MODIFY,MUTEX,MYSQL_ERRNO,NAME,NAMES,NATURAL,NCHAR,NESTED,' + - 'NETWORK_NAMESPACE,NEVER,NEW,NEXT,NO,NODEGROUP,NONE,NOT,NOWAIT,NO_WAIT,' + - 'NO_WRITE_TO_BINLOG,NTH_VALUE,NTILE,NULL,NULLS,NUMBER,NVARCHAR,OF,OFF,' + - 'OFFSET,OJ,OLD,ON,ONE,ONLY,OPEN,OPTIMIZE,OPTIMIZER_COSTS,OPTION,OPTIONAL,' + - 'OPTIONALLY,OPTIONS,OR,ORDER,ORDINALITY,ORGANIZATION,OTHERS,OUT,OUTER,' + - 'OUTFILE,OVER,OWNER,PACK_KEYS,PAGE,PARSER,PARTIAL,PARTITION,PARTITIONING,' + - 'PARTITIONS,PASSWORD_LOCK_TIME,PATH,PERCENT_RANK,PERSIST,PERSIST_ONLY,' + - 'PHASE,PLUGIN,PLUGINS,PLUGIN_DIR,PORT,PRECEDES,PRECEDING,PREPARE,PRESERVE,' + - 'PREV,PRIMARY,PRIVILEGES,PRIVILEGE_CHECKS_USER,PROCEDURE,PROCESS,' + - 'PROCESSLIST,PROFILE,PROFILES,PROXY,PURGE,QUERY,QUICK,RANDOM,RANGE,RANK,' + - 'READ,READS,READ_ONLY,READ_WRITE,REBUILD,RECOVER,RECURSIVE,REDOFILE,' + - 'REDO_BUFFER_SIZE,REFERENCE,REFERENCES,REGEXP,REGISTRATION,RELAY,RELAYLOG,' + - 'RELAY_LOG_FILE,RELAY_LOG_POS,RELAY_THREAD,RELEASE,RELOAD,REMOTE,REMOVE,' + - 'RENAME,REORGANIZE,REPAIR,REPEATABLE,REPLACE,REPLICA,REPLICAS,' + - 'REPLICATE_DO_DB,REPLICATE_DO_TABLE,REPLICATE_IGNORE_DB,' + - 'REPLICATE_IGNORE_TABLE,REPLICATE_REWRITE_DB,REPLICATE_WILD_DO_TABLE,' + - 'REPLICATE_WILD_IGNORE_TABLE,REPLICATION,REQUIRE,REQUIRE_ROW_FORMAT,RESET,' + - 'RESIGNAL,RESOURCE,RESPECT,RESTART,RESTORE,RESTRICT,RESUME,RETAIN,RETURN,' + - 'RETURNED_SQLSTATE,RETURNING,RETURNS,REUSE,REVOKE,RLIKE,ROLE,ROLLBACK,' + - 'ROLLUP,ROTATE,ROUTINE,ROW,ROWS,ROW_FORMAT,ROW_NUMBER,RTREE,SAVEPOINT,' + - 'SCHEDULE,SCHEMA,SCHEMAS,SCHEMA_NAME,SECONDARY,SECONDARY_ENGINE,' + - 'SECONDARY_ENGINE_ATTRIBUTE,SECONDARY_LOAD,SECONDARY_UNLOAD,' + - 'SECOND_MICROSECOND,SECURITY,SELECT,SENSITIVE,SEPARATOR,SERIALIZABLE,' + - 'SERVER,SESSION,SET,SHARE,SHOW,SHUTDOWN,SIGNAL,SIMPLE,SKIP,SLAVE,SLOW,' + - 'SNAPSHOT,SOCKET,SOME,SONAME,SOUNDS,SOURCE,SOURCE_AUTO_POSITION,' + + 'FUNCTION,GENERAL,GENERATE,GENERATED,GEOMCOLLECTION,GET,' + + 'GET_MASTER_PUBLIC_KEY,GET_SOURCE_PUBLIC_KEY,GLOBAL,GRANT,GRANTS,GROUP,' + + 'GROUPING,GROUPS,GROUP_REPLICATION,GTID_ONLY,HAVING,HELP,HIGH_PRIORITY,' + + 'HISTOGRAM,HISTORY,HOST,HOSTS,HOUR_MICROSECOND,HOUR_MINUTE,HOUR_SECOND,' + + 'IDENTIFIED,IGNORE,IGNORE_SERVER_IDS,IMPORT,IN,INACTIVE,INDEX,INDEXES,' + + 'INFILE,INITIAL,INITIAL_SIZE,INITIATE,INNER,INOUT,INSENSITIVE,INSERT,' + + 'INSERT_METHOD,INSTALL,INSTANCE,INT1,INT2,INT3,INT4,INT8,INTERSECT,INTO,' + + 'INVISIBLE,INVOKER,IO,IO_AFTER_GTIDS,IO_BEFORE_GTIDS,IO_THREAD,IPC,IS,' + + 'ISOLATION,ISSUER,JOIN,JSON,JSON_TABLE,JSON_VALUE,KEY,KEYRING,KEYS,' + + 'KEY_BLOCK_SIZE,KILL,LAG,LANGUAGE,LAST,LAST_VALUE,LATERAL,LEAD,LEADING,' + + 'LEAVES,LESS,LEVEL,LIKE,LIMIT,LINEAR,LINES,LIST,LOAD,LOCAL,LOCK,LOCKED,' + + 'LOCKS,LOGFILE,LOGS,LONG,LOW_PRIORITY,MASTER,MASTER_AUTO_POSITION,' + + 'MASTER_BIND,MASTER_COMPRESSION_ALGORITHMS,MASTER_CONNECT_RETRY,' + + 'MASTER_DELAY,MASTER_HEARTBEAT_PERIOD,MASTER_HOST,MASTER_LOG_FILE,' + + 'MASTER_LOG_POS,MASTER_PASSWORD,MASTER_PORT,MASTER_PUBLIC_KEY_PATH,' + + 'MASTER_RETRY_COUNT,MASTER_SERVER_ID,MASTER_SSL,MASTER_SSL_CA,' + + 'MASTER_SSL_CAPATH,MASTER_SSL_CERT,MASTER_SSL_CIPHER,MASTER_SSL_CRL,' + + 'MASTER_SSL_CRLPATH,MASTER_SSL_KEY,MASTER_SSL_VERIFY_SERVER_CERT,' + + 'MASTER_TLS_CIPHERSUITES,MASTER_TLS_VERSION,MASTER_USER,' + + 'MASTER_ZSTD_COMPRESSION_LEVEL,MATCH,MAXVALUE,MAX_CONNECTIONS_PER_HOUR,' + + 'MAX_QUERIES_PER_HOUR,MAX_ROWS,MAX_SIZE,MAX_UPDATES_PER_HOUR,' + + 'MAX_USER_CONNECTIONS,MEDIUM,MEMBER,MESSAGE_TEXT,MIDDLEINT,MIGRATE,' + + 'MINUTE_MICROSECOND,MINUTE_SECOND,MIN_ROWS,MOD,MODE,MODIFIES,MODIFY,MUTEX,' + + 'MYSQL_ERRNO,NAME,NAMES,NATURAL,NCHAR,NESTED,NETWORK_NAMESPACE,NEVER,NEW,' + + 'NEXT,NO,NODEGROUP,NONE,NOT,NOWAIT,NO_WAIT,NO_WRITE_TO_BINLOG,NTH_VALUE,' + + 'NTILE,NULL,NULLS,NUMBER,NVARCHAR,OF,OFF,OFFSET,OJ,OLD,ON,ONE,ONLY,OPEN,' + + 'OPTIMIZE,OPTIMIZER_COSTS,OPTION,OPTIONAL,OPTIONALLY,OPTIONS,OR,ORDER,' + + 'ORDINALITY,ORGANIZATION,OTHERS,OUT,OUTER,OUTFILE,OVER,OWNER,PACK_KEYS,' + + 'PAGE,PARSER,PARSE_GCOL_EXPR,PARTIAL,PARTITION,PARTITIONING,PARTITIONS,' + + 'PASSWORD_LOCK_TIME,PATH,PERCENT_RANK,PERSIST,PERSIST_ONLY,PHASE,PLUGIN,' + + 'PLUGINS,PLUGIN_DIR,PORT,PRECEDES,PRECEDING,PREPARE,PRESERVE,PREV,PRIMARY,' + + 'PRIVILEGES,PRIVILEGE_CHECKS_USER,PROCEDURE,PROCESS,PROCESSLIST,PROFILE,' + + 'PROFILES,PROXY,PURGE,QUERY,QUICK,RANDOM,RANGE,RANK,READ,READS,READ_ONLY,' + + 'READ_WRITE,REBUILD,RECOVER,RECURSIVE,REDOFILE,REDO_BUFFER_SIZE,REFERENCE,' + + 'REFERENCES,REGEXP,REGISTRATION,RELAY,RELAYLOG,RELAY_LOG_FILE,' + + 'RELAY_LOG_POS,RELAY_THREAD,RELEASE,RELOAD,REMOTE,REMOVE,RENAME,REORGANIZE,' + + 'REPAIR,REPEATABLE,REPLACE,REPLICA,REPLICAS,REPLICATE_DO_DB,' + + 'REPLICATE_DO_TABLE,REPLICATE_IGNORE_DB,REPLICATE_IGNORE_TABLE,' + + 'REPLICATE_REWRITE_DB,REPLICATE_WILD_DO_TABLE,REPLICATE_WILD_IGNORE_TABLE,' + + 'REPLICATION,REQUIRE,REQUIRE_ROW_FORMAT,RESET,RESIGNAL,RESOURCE,RESPECT,' + + 'RESTART,RESTORE,RESTRICT,RESUME,RETAIN,RETURN,RETURNED_SQLSTATE,RETURNING,' + + 'RETURNS,REUSE,REVOKE,RLIKE,ROLE,ROLLBACK,ROLLUP,ROTATE,ROUTINE,ROW,ROWS,' + + 'ROW_FORMAT,ROW_NUMBER,RTREE,SAVEPOINT,SCHEDULE,SCHEMA,SCHEMAS,SCHEMA_NAME,' + + 'SECONDARY,SECONDARY_ENGINE,SECONDARY_ENGINE_ATTRIBUTE,SECONDARY_LOAD,' + + 'SECONDARY_UNLOAD,SECOND_MICROSECOND,SECURITY,SELECT,SENSITIVE,SEPARATOR,' + + 'SERIALIZABLE,SERVER,SESSION,SET,SHARE,SHOW,SHUTDOWN,SIGNAL,SIMPLE,SKIP,' + + 'SLAVE,SLOW,SNAPSHOT,SOCKET,SOME,SONAME,SOUNDS,SOURCE,SOURCE_AUTO_POSITION,' + 'SOURCE_BIND,SOURCE_COMPRESSION_ALGORITHMS,SOURCE_CONNECT_RETRY,' + 'SOURCE_DELAY,SOURCE_HEARTBEAT_PERIOD,SOURCE_HOST,SOURCE_LOG_FILE,' + 'SOURCE_LOG_POS,SOURCE_PASSWORD,SOURCE_PORT,SOURCE_PUBLIC_KEY_PATH,' + @@ -1017,10 +1017,10 @@ implementation 'TABLE_CHECKSUM,TABLE_NAME,TEMPORARY,TERMINATED,THAN,THREAD_PRIORITY,TIES,' + 'TLS,TO,TRAILING,TRANSACTION,TRIGGER,TRIGGERS,TRUE,TYPE,TYPES,UNBOUNDED,' + 'UNCOMMITTED,UNDO,UNDOFILE,UNDO_BUFFER_SIZE,UNINSTALL,UNION,UNIQUE,UNKNOWN,' + - 'UNLOCK,UNREGISTER,UPDATE,UPGRADE,USAGE,USE,USER_RESOURCES,USE_FRM,USING,' + - 'VALIDATION,VALUE,VALUES,VARCHARACTER,VARIABLES,VARYING,VCPU,VIEW,VIRTUAL,' + - 'VISIBLE,WAIT,WARNINGS,WHERE,WINDOW,WITH,WITHOUT,WORK,WRAPPER,WRITE,X509,' + - 'XA,XID,XML,XOR,YEAR_MONTH,ZONE'; + 'UNLOCK,UNREGISTER,UPDATE,UPGRADE,URL,USAGE,USE,USER_RESOURCES,USE_FRM,' + + 'USING,VALIDATION,VALUE,VALUES,VARCHARACTER,VARIABLES,VARYING,VCPU,VIEW,' + + 'VIRTUAL,VISIBLE,WAIT,WARNINGS,WHERE,WINDOW,WITH,WITHOUT,WORK,WRAPPER,' + + 'WRITE,X509,XA,XID,XML,XOR,YEAR_MONTH,ZONE'; // PLSQL keywords MySQLPLSQLKW: UnicodeString = @@ -1042,7 +1042,7 @@ implementation 'longtext,mediumblob,mediumint,mediumtext,multilinestring,multipoint,' + 'multipolygon,national,numeric,point,polygon,precision,real,serial,' + 'signed,smallint,string,text,time,timestamp,tinyblob,tinyint,tinytext,' + - 'unicode,unsigned,varbinary,varchar,year,zerofill,' + + 'unicode,unsigned,varbinary,varchar,vector,year,zerofill,' + // Row Formats 'COMPACT,COMPRESSED,DISK,DYNAMIC,FIXED,REDUNDANT,' + diff --git a/components/virtualtreeview/.gitignore b/components/virtualtreeview/.gitignore index 3b7621d64..b38a0e1d6 100644 --- a/components/virtualtreeview/.gitignore +++ b/components/virtualtreeview/.gitignore @@ -1,54 +1,56 @@ -# Compiled source # -################### -*.dcu -*.obj -*.exe -*.bpl -*.bpi -*.dcp -*.rsm -*.stat -*.map -*.d -*.o - -# Generated source # -################### -*.hpp - -# Backup files # -################### -*.~* - -# IDE Files # -################### -*.dproj.local -*.groupproj.local -*.identcache -*.dsk -*.tvsconfig -*.otares -*.drc -*.rc -*.res -*.local - -# Output Folders # -################### -/Win32 -/Win64 -/OSX32 -/__history -*.bak -*.Patch -VirtualTreeView.zip -*.#00 -*.pch -*.skincfg -*.a -Packages/RAD Studio XE3/VirtualTreesR.lib -*.lib - -# Folder with repro projects # -############################## +# Compiled source # +################### +*.dcu +*.obj +*.exe +*.bpl +*.bpi +*.dcp +*.rsm +*.stat +*.map +*.d +*.o + +# Generated source # +################### +*.hpp + +# Backup files # +################### +*.~* +__recovery + +# IDE Files # +################### +*.dproj.local +*.groupproj.local +*.identcache +*.dsk +*.tvsconfig +*.otares +*.drc +*.rc +*.res +*.local +*.dsv + +# Output Folders # +################### +/Win32 +/Win64 +/OSX32 +/__history +*.bak +*.Patch +VirtualTreeView.zip +*.#00 +*.pch +*.skincfg +*.a +Packages/RAD Studio XE3/VirtualTreesR.lib +*.lib + +# Folder with repro projects # +############################## /#* \ No newline at end of file diff --git a/components/virtualtreeview/Design/VirtualTreesReg.pas b/components/virtualtreeview/Design/VirtualTreesReg.pas index d5b67a665..9e241fcc7 100644 --- a/components/virtualtreeview/Design/VirtualTreesReg.pas +++ b/components/virtualtreeview/Design/VirtualTreesReg.pas @@ -11,8 +11,7 @@ interface {$warn UNSAFE_CODE off} uses - Windows, Classes, DesignIntf, DesignEditors, VCLEditors, PropertyCategories, - ColnEdit, VirtualTrees, VirtualTrees.HeaderPopup; + DesignEditors; type TVirtualTreeEditor = class (TDefaultEditor) @@ -27,8 +26,12 @@ procedure Register; implementation uses - StrEdit, Dialogs, TypInfo, SysUtils, Graphics, CommCtrl, ImgList, Controls, - VirtualTrees.ClipBoard, VirtualTrees.Actions; + WinApi.Windows, WinApi.CommCtrl, + System.TypInfo, System.SysUtils, System.Classes, + StrEdit,DesignIntf, VCLEditors, PropertyCategories, ColnEdit, + Vcl.Dialogs, Vcl.Graphics, Vcl.ImgList, Vcl.Controls, + VirtualTrees.ClipBoard, VirtualTrees.Actions, VirtualTrees, VirtualTrees.DrawTree, + VirtualTrees.HeaderPopup, VirtualTrees.BaseTree; type // The usual trick to make a protected property accessible in the ShowCollectionEditor call below. diff --git a/components/virtualtreeview/INSTALL.txt b/components/virtualtreeview/INSTALL.txt index 5bebc4c66..162fac3d9 100644 --- a/components/virtualtreeview/INSTALL.txt +++ b/components/virtualtreeview/INSTALL.txt @@ -1,22 +1,27 @@ -Supported Delphi version: RAD Studio XE3 and higher -Supported Windows Versions: Windows Vista and higher +Supported Delphi version: RAD Studio 10.0 and higher +Supported Windows Versions: Windows 8 and higher Extract the entire(!) ZIP file and follow the instructions below. Delphi / RAD Studio 10.4 and higher Installation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Open the project group "Packages\RAD Studio 10.4+\VirtualTreeView.groupproj" -2. Right click on "VirtualTreesD*.bpl" and click "Install" -3. Go to "Tools > Options > Language > Delphi Options > Library > Library Path > [...]" - Browse to the "Source" folder of VirtualTreeView, press "OK", "Add", "OK" - Do this for both Win32 and Win64 platform, which you can choose in the dropdown box. -4. C++ Builder users only: - In the Options dialog go to "Environment Options > C++ Options > Paths and Directories" - a) Click "Library Path > [...]" - Browse to the "Source" folder of VirtualTreeView, press "OK", "Add", "OK" - b) Click "System Include path > [...]" - Browse to the "Source" folder of VirtualTreeView, press "OK", "Add", "OK" -5. Close the RAD Studio Options dialog by clicking "Save". +2. Right click on root element "VirtualTreeView" and click "Build All" +3. Right click on "VirtualTreesD*.bpl" and click "Install" +4. Go to "Tools > Options > Language > Delphi Options > Library +5. Choose platform "Win32", click on "Library Path > [...]" + Browse to the "Packages\RAD Studio 10.4+\Win32\Release" folder of VirtualTreeView, + press "Choose Folder", "Add", "OK" +6. Choose platform "Win64", click on "Library Path > [...]" + Browse to the "Packages\RAD Studio 10.4+\Win64\Release" folder of VirtualTreeView, + press "Choose Folder", "Add", "OK" +7. C++ Builder users only: + In the Options dialog go to "Environment Options > C++ Options > Paths and Directories" + a) Click "Library Path > [...]" + Browse to the "Source" folder of VirtualTreeView, press "OK", "Add", "OK" + b) Click "System Include path > [...]" + Browse to the "Source" folder of VirtualTreeView, press "OK", "Add", "OK" +8. Close the RAD Studio Options dialog by clicking "Save". Delphi / RAD Studio 10.3 @@ -25,7 +30,7 @@ Delphi / RAD Studio 10.3 2. Right click on "VirtualTreesD270.bpl" and click "Install" 3. Go to "Tools > Options > Language > Delphi Options > Library > Library Path > [...]" Browse to the "Source" folder of VirtualTreeView, press "OK", "Add", "OK" - Do this for both Win32 and Win64 platform, which you can choose in the dropdown box. + Do this for both Win32 and Win64 platforms, which you can choose in the dropdown box. 4. C++ Builder users only: In the Options dialog go to "Environment Options > C++ Options > Paths and Directories" a) Click "Library Path > [...]" @@ -35,13 +40,13 @@ Delphi / RAD Studio 10.3 5. Close the RAD Studio Options dialog by clicking "Save". -Delphi / RAD Studio XE3 - 10.2 Installation +Delphi / RAD Studio 10.0 - 10.2 Installation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Open the project group "Packages\RAD Studio *\VirtualTreeView.groupproj" 2. Right click on "VirtualTreesD*.bpl" and click "Install" 3. Go to "Tools > Options > Environment Options > Delphi Options > Library > Library Path > [...]" Browse to the "Source" folder of VirtualTreeView, press "OK", "Add", "OK" - Do this for both Win32 and Win64 platform, which you can choose in the dropdown box. + Do this for both Win32 and Win64 platforms, which you can choose in the dropdown box. 4. C++ Builder users only: In the Options dialog go to "Environment Options > C++ Options > Paths and Directories" a) Click "Library Path > [...]" @@ -62,21 +67,5 @@ In case you experience any problems, try to delete all these files from your dis I recommend using UltraSearch for this task: http://www.jam-software.de/ultrasearch/ -Please send comments and suggestions regarding the packages and the install -instructions to joachim.marder@gmail.com or open an issue. - - -C++ Builder XE3 and higher Installation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -1. Open the project group "Packages\CBuilder XE*\VirtualTreeView.groupproj" - that is closest to your version. -2. Right click on "VirtualTreesR*.bpl" and click "Build" -3. Right click on "VirtualTreesD*.bpl" and click "Install" -4. Go to "Tools > Options > Environment Options > Delphi Options > Library > Library Path > [...]" - Browse to the "Source" folder of VirtualTreeView, press "OK", "Add", "OK", "OK" -5. Go to "Tools > Options > Environment Options > C++ Options > Paths and Directories" - a) Click "Library Path > [...]" - Browse to the "Source" folder of VirtualTreeView, press "OK", "Add", "OK" - b) Click "System Include path > [...]" - Browse to the "Source" folder of VirtualTreeView, press "OK", "Add", "OK", "OK" -6. If you target Win64 you need to build VirtualTreesR*.bpl also for the platform "Win64" +For comments and suggestions regarding the packages and the install +instructions open an Issue at: https://github.com/JAM-Software/Virtual-TreeView/issues diff --git a/components/virtualtreeview/MAKEFILE b/components/virtualtreeview/MAKEFILE index aba6f01c9..4c89b54b1 100644 --- a/components/virtualtreeview/MAKEFILE +++ b/components/virtualtreeview/MAKEFILE @@ -1,21 +1,30 @@ -PROJECT = VirtualTrees -EMBARCADERO = $(PROGRAMFILES)\Embarcadero\RAD Studio -STUDIO = $(PROGRAMFILES)\Embarcadero\Studio -BDSCOMMONDIRMAIN = %PUBLIC%\Documents\Embarcadero\Studio +# Program files folder +PROGRAMFILESX64 = $(PROGRAMFILES) +!IF EXIST("C:\Program Files (x86)") +PROGRAMFILES = C:\Program Files (x86) +PROGRAMFILESX64 = C:\Program Files +!ENDIF + # Default MS Build version !IF EXIST("$(PROGRAMFILESX64)\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\msbuild.exe") BUILDEXE = "$(PROGRAMFILESX64)\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\msbuild.exe" +!ELSE IF EXIST("$(PROGRAMFILESX64)\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\msbuild.exe") +BUILDEXE = "$(PROGRAMFILESX64)\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\msbuild.exe" +!ELSE IF EXIST("$(PROGRAMFILESX64)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe") +BUILDEXE = "$(PROGRAMFILESX64)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe" +!ELSE IF EXIST("$(PROGRAMFILESX64)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\msbuild.exe") +BUILDEXE = "$(PROGRAMFILESX64)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\msbuild.exe" +!ELSE IF EXIST("$(PROGRAMFILESX64)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\msbuild.exe") +BUILDEXE = "$(PROGRAMFILESX64)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\msbuild.exe" !ELSE -!IF EXIST("$(PROGRAMFILES)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe") -BUILDEXE = "$(PROGRAMFILES)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe" -!ELSE -!IF EXIST("$(PROGRAMFILES)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\msbuild.exe") -BUILDEXE = "$(PROGRAMFILES)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\msbuild.exe" -!ELSE -BUILDEXE = "$(PROGRAMFILES)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\msbuild.exe" -!ENDIF -!ENDIF +BUILDEXE = "msbuild.exe" !ENDIF + +PROJECT = VirtualTrees +EMBARCADERO = $(PROGRAMFILES)\Embarcadero\RAD Studio +STUDIO = $(PROGRAMFILES)\Embarcadero\Studio +BDSCOMMONDIRMAIN = %PUBLIC%\Documents\Embarcadero\Studio + BUILD = $(BUILDEXE) /t:Rebuild clean: @@ -24,6 +33,21 @@ clean: DEL /S /Q .\*.DCU #TODO: Add demos and package folders +12.3: Source\*.pas "Packages\RAD Studio 10.4+\$(PROJECT)R.dpk" "Packages\RAD Studio 10.4+\$(PROJECT)R.dproj" "Packages\RAD Studio 10.4+\$(PROJECT)D.dpk" "Packages\RAD Studio 10.4+\$(PROJECT)D.dproj" + SET BDS=$(STUDIO)\23.0 + $(BUILD) /property:Platform=Win32 "Packages\RAD Studio 10.4+\$(PROJECT)R.dproj" + $(BUILD) /property:Platform=Win32 "Packages\RAD Studio 10.4+\$(PROJECT)D.dproj" + $(BUILD) /property:Platform=Win64 "Packages\RAD Studio 10.4+\$(PROJECT)R.dproj" + $(BUILD) /property:Platform=Win64 "Packages\RAD Studio 10.4+\$(PROJECT)D.dproj" + $(MAKE) _samples + +12.0: Source\*.pas "Packages\RAD Studio 10.4+\$(PROJECT)R.dpk" "Packages\RAD Studio 10.4+\$(PROJECT)R.dproj" "Packages\RAD Studio 10.4+\$(PROJECT)D.dpk" "Packages\RAD Studio 10.4+\$(PROJECT)D.dproj" + SET BDS=$(STUDIO)\23.0 + $(BUILD) "Packages\RAD Studio 10.4+\$(PROJECT)R.dproj" + $(BUILD) "Packages\RAD Studio 10.4+\$(PROJECT)D.dproj" + $(BUILD) /property:Platform=Win64 "Packages\RAD Studio 10.4+\$(PROJECT)R.dproj" + $(MAKE) _samples + # build all packages for Delphi 10.4. Note: The variable $@ is expanded to the build target name 10.1 10.2 10.3 10.4+: Source\*.pas "Packages\RAD Studio $@\$(PROJECT)R.dpk" "Packages\RAD Studio $@\$(PROJECT)R.dproj" "Packages\RAD Studio $@\$(PROJECT)D.dpk" "Packages\RAD Studio $@\$(PROJECT)D.dproj" SET BDS=$(STUDIO)\21.0 @@ -46,7 +70,7 @@ clean: _samples: "Demos\Advanced\Advanced.exe" "Demos\Minimal\Minimal.exe" "Demos\Objects\Objects.exe" "Demos\OLE\OLE.exe" -_continuousbuilds: clean 10.4+ +_continuousbuilds: clean 12.3 _release: #This small batch file is intended to create a source code release file of the VirtualTreeView as ZIP archive @@ -56,4 +80,4 @@ _release: ECHO Source code zip archive "VirtualTreeView.zip" created. ECHO !!! Please add version number to ZIP file name!!! ECHO !!! Please create release at: https://github.com/Virtual-TreeView/Virtual-TreeView/releases - ECHO !!! Let JAM web-team upload the file to our server at https://www.jam-software.com/virtual-treeview + ECHO !!! Let JAM web-team upload the file to our server at https://www.jam-software.com/virtual-treeview \ No newline at end of file diff --git a/components/virtualtreeview/MakeRelease.Bat b/components/virtualtreeview/MakeRelease.Bat deleted file mode 100644 index b016c9356..000000000 --- a/components/virtualtreeview/MakeRelease.Bat +++ /dev/null @@ -1,3 +0,0 @@ -@ECHO OFF -nmake _release -pause \ No newline at end of file diff --git a/components/virtualtreeview/README.md b/components/virtualtreeview/README.md index d05cef65f..a882caa8f 100644 --- a/components/virtualtreeview/README.md +++ b/components/virtualtreeview/README.md @@ -5,7 +5,9 @@ Virtual Treeview is a Delphi treeview control built from ground up. Many years o I don't use C++ Builder and my experience with it is very limited. This makes it difficult to take care about bugs that are reported in C++ Builder and to maintain the C++ Builder packages. I would be great if someone would volunteer to do this. ### Downloads -**V7.6.x** official release for **RAD Studio XE3 to 10.4.2 Rio**: [JAM Software](https://www.jam-software.com/virtual-treeview/VirtualTreeView.zip) +[**V8** official release](https://github.com/JAM-Software/Virtual-TreeView/releases/latest) for **RAD Studio 10 to 12** which includes some **[breaking changes](https://github.com/JAM-Software/Virtual-TreeView/wiki/Breaking-Changes-in-V8)**. + +[**V7.6.x**](https://github.com/JAM-Software/Virtual-TreeView/releases/tag/V7.6.6) for **Delphi XE3 to XE8**. An experimental **FireMonkey** port can be found here: [livius2/Virtual-TreeView](https://github.com/livius2/Virtual-TreeView) @@ -13,9 +15,7 @@ A port to **Lazarus / FPC** can be found here: [blikblum/VirtualTreeView-Lazarus For a **Delphi XE2** compatible fork see: [Fr0sT-Brutal/VirtualTreeView_mod/tree/fr0st_xe2](https://github.com/Fr0sT-Brutal/VirtualTreeView_mod/tree/fr0st_xe2) -For a **Delphi XE** compatible fork see: [sglienke/Virtual-TreeView](https://github.com/sglienke/Virtual-TreeView) - -**V5.5.3** for **Delphi 7 to XE2**: [Download](http://www.jam-software.com/virtual-treeview/VirtualTreeViewV5.5.3.zip) +**V5.5.3** for **Delphi 7 to XE2**: [Download](https://github.com/JAM-Software/Virtual-TreeView/releases/download/V5.5.3/VirtualTreeViewV5.5.3.zip) **V6 latest stable version** tested on Windows XP/2003 support: [GitHub](https://github.com/Virtual-TreeView/Virtual-TreeView/archive/V6_stable.zip) diff --git a/components/virtualtreeview/Source/VirtualTrees.Accessibility.pas b/components/virtualtreeview/Source/VirtualTrees.Accessibility.pas index 04b516d2a..cb1e46896 100644 --- a/components/virtualtreeview/Source/VirtualTrees.Accessibility.pas +++ b/components/virtualtreeview/Source/VirtualTrees.Accessibility.pas @@ -8,8 +8,10 @@ interface uses - Winapi.Windows, System.Classes, Winapi.ActiveX, System.Types, Winapi.oleacc, - VirtualTrees, VirtualTrees.AccessibilityFactory, Vcl.Controls; + Winapi.Windows, Winapi.ActiveX, Winapi.oleacc, + System.Classes, System.Types, + Vcl.Controls, + VirtualTrees, VirtualTrees.AccessibilityFactory, VirtualTrees.BaseTree; type TVirtualTreeAccessibility = class(TInterfacedObject, IDispatch, IAccessible) @@ -99,7 +101,9 @@ TVTMultiColumnAccessibleItemProvider = class(TInterfacedObject, IVTAccessibleP implementation uses - System.SysUtils, Vcl.Forms, System.Variants, System.Math; + System.SysUtils, System.Variants, System.Math, + Vcl.Forms, + VirtualTrees.Types; type @@ -792,6 +796,3 @@ initialization TVirtualTreeAccessibility.RegisterDefaultAccessibleProviders(); end. - - - diff --git a/components/virtualtreeview/Source/VirtualTrees.AccessibilityFactory.pas b/components/virtualtreeview/Source/VirtualTrees.AccessibilityFactory.pas index d62703132..e26dacaf8 100644 --- a/components/virtualtreeview/Source/VirtualTrees.AccessibilityFactory.pas +++ b/components/virtualtreeview/Source/VirtualTrees.AccessibilityFactory.pas @@ -1,178 +1,181 @@ -unit VirtualTrees.AccessibilityFactory; - -// The contents of this file are subject to the Mozilla Public License -// Version 1.1 (the "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ -// -// Alternatively, you may redistribute this library, use and/or modify it under the terms of the -// GNU Lesser General Public License as published by the Free Software Foundation; -// either version 2.1 of the License, or (at your option) any later version. -// You may obtain a copy of the LGPL at http://www.gnu.org/copyleft/. -// -// Software distributed under the License is distributed on an "AS IS" basis, -// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -// specific language governing rights and limitations under the License. -// -// The original code is VirtualTrees.pas, released September 30, 2000. -// -// The initial developer of the original code is digital publishing AG (Munich, Germany, www.digitalpublishing.de), -// written by Mike Lischke (public@soft-gems.net, www.soft-gems.net). -// -// Portions created by digital publishing AG are Copyright -// (C) 1999-2001 digital publishing AG. All Rights Reserved. -//---------------------------------------------------------------------------------------------------------------------- - - -// class to create IAccessibles for the tree passed into it. -// If not already assigned, creates IAccessibles for the tree itself -// and the focused item -// the tree accessible is returned when the tree receives an WM_GETOBJECT message -// the AccessibleItem is returned when the Accessible is being asked for the first child -// To create your own IAccessibles, use the VTStandardAccessible unit as a reference, -// and assign your Accessibles to the variables in the unit's initialization. -// You only need to add the unit to your project, and voil, you have an accessible string tree! -// -// Written by Marco Zehe. (c) 2007 - -interface - -uses - System.Classes, Winapi.oleacc, VirtualTrees; - -type - IVTAccessibleProvider = interface - function CreateIAccessible(ATree: TBaseVirtualTree): IAccessible; - end; - - TVTAccessibilityFactory = class(TObject) - strict private class var - FAccessibilityAvailable: Boolean; - FVTAccessibleFactory: TVTAccessibilityFactory; - strict private - FAccessibleProviders: TInterfaceList; - private - class procedure FreeFactory; - public - constructor Create; - destructor Destroy; override; - function CreateIAccessible(ATree: TBaseVirtualTree): IAccessible; - class function GetAccessibilityFactory: TVTAccessibilityFactory; static; - procedure RegisterAccessibleProvider(const AProvider: IVTAccessibleProvider); - procedure UnRegisterAccessibleProvider(const AProvider: IVTAccessibleProvider); - end; - - -implementation - -{ TVTAccessibilityFactory } - -constructor TVTAccessibilityFactory.Create; -begin - inherited Create; - FAccessibleProviders := TInterfaceList.Create; - FAccessibleProviders.Clear; -end; - -function TVTAccessibilityFactory.CreateIAccessible( - ATree: TBaseVirtualTree): IAccessible; -var - I: Integer; - TmpIAccessible: IAccessible; -// returns an IAccessible. -// 1. If the Accessible property of the passed-in tree is nil, -// the first registered element will be returned. -// Usually, this is the IAccessible that provides information about the tree itself. -// If it is not nil, we'll check whether the AccessibleItem is nil. -// If it is, we'll look in the registered IAccessibles for the appropriate one. -// Each IAccessibleProvider will check the tree for properties to determine whether it is responsible. -// We'll work top to bottom, from the most complicated to the most simple. -// The index for these should all be greater than 0, e g the IAccessible for the tree itself should always be registered first, then any IAccessible items. -begin - Result := nil; - if ATree <> nil then - begin - if ATree.Accessible = nil then - begin - if FAccessibleProviders.Count > 0 then - begin - Result := IVTAccessibleProvider(FAccessibleProviders.Items[0]).CreateIAccessible(ATree); - Exit; - end; - end; - if ATree.AccessibleItem = nil then - begin - if FAccessibleProviders.Count > 0 then - begin - for I := FAccessibleProviders.Count - 1 downto 1 do - begin - TmpIAccessible := IVTAccessibleProvider(FAccessibleProviders.Items[I]).CreateIAccessible(ATree); - if TmpIAccessible <> nil then - begin - Result := TmpIAccessible; - Break; - end; - end; - if TmpIAccessible = nil then - begin - Result := IVTAccessibleProvider(FAccessibleProviders.Items[0]).CreateIAccessible(ATree); - end; - end; - end - else - Result := ATree.AccessibleItem; - end; -end; - -destructor TVTAccessibilityFactory.Destroy; -begin - FAccessibleProviders.Free; - FAccessibleProviders := nil; - inherited Destroy; -end; - -class procedure TVTAccessibilityFactory.FreeFactory; -begin - FVTAccessibleFactory.Free; -end; - -procedure TVTAccessibilityFactory.RegisterAccessibleProvider(const AProvider: IVTAccessibleProvider); -// Ads a provider if it is not already registered -begin - if FAccessibleProviders.IndexOf(AProvider) < 0 then - FAccessibleProviders.Add(AProvider) -end; - -procedure TVTAccessibilityFactory.UnRegisterAccessibleProvider(const AProvider: IVTAccessibleProvider); -// Unregisters/removes an IAccessible provider if it is present -begin - if FAccessibleProviders.IndexOf(AProvider) >= 0 then - FAccessibleProviders.Remove(AProvider); -end; - -class function TVTAccessibilityFactory.GetAccessibilityFactory: TVTAccessibilityFactory; -// Accessibility helper function to create a singleton class that will create or return -// the IAccessible interface for the tree and the focused node. - -begin - // first, check if we've loaded the library already - if not FAccessibilityAvailable then - FAccessibilityAvailable := True; - if FAccessibilityAvailable then - begin - // Check to see if the class has already been created. - if FVTAccessibleFactory = nil then - FVTAccessibleFactory := TVTAccessibilityFactory.Create; - Result := FVTAccessibleFactory; - end - else - Result := nil; -end; - -initialization - -finalization - TVTAccessibilityFactory.FreeFactory; - -end. - - +unit VirtualTrees.AccessibilityFactory; + +// The contents of this file are subject to the Mozilla Public License +// Version 1.1 (the "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ +// +// Alternatively, you may redistribute this library, use and/or modify it under the terms of the +// GNU Lesser General Public License as published by the Free Software Foundation; +// either version 2.1 of the License, or (at your option) any later version. +// You may obtain a copy of the LGPL at http://www.gnu.org/copyleft/. +// +// Software distributed under the License is distributed on an "AS IS" basis, +// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +// specific language governing rights and limitations under the License. +// +// The original code is VirtualTrees.pas, released September 30, 2000. +// +// The initial developer of the original code is digital publishing AG (Munich, Germany, www.digitalpublishing.de), +// written by Mike Lischke (public@soft-gems.net, www.soft-gems.net). +// +// Portions created by digital publishing AG are Copyright +// (C) 1999-2001 digital publishing AG. All Rights Reserved. +//---------------------------------------------------------------------------------------------------------------------- + + +// class to create IAccessibles for the tree passed into it. +// If not already assigned, creates IAccessibles for the tree itself +// and the focused item +// the tree accessible is returned when the tree receives an WM_GETOBJECT message +// the AccessibleItem is returned when the Accessible is being asked for the first child +// To create your own IAccessibles, use the VTStandardAccessible unit as a reference, +// and assign your Accessibles to the variables in the unit's initialization. +// You only need to add the unit to your project, and voilá, you have an accessible string tree! +// +// Written by Marco Zehe. (c) 2007 + +interface + +uses + Winapi.oleacc, + System.Classes, + Vcl.Controls, + VirtualTrees.BaseTree; + +type + IVTAccessibleProvider = interface + ['{8B76176B-C1F2-4C5C-99B4-2444FABE495C}'] + function CreateIAccessible(ATree: TBaseVirtualTree): IAccessible; + end; + + TVTAccessibilityFactory = class(TObject) + strict private class var + FAccessibilityAvailable: Boolean; + FVTAccessibleFactory: TVTAccessibilityFactory; + strict private + FAccessibleProviders: TInterfaceList; + private + class procedure FreeFactory; + public + constructor Create; + destructor Destroy; override; + function CreateIAccessible(ATree: TCustomControl): IAccessible; + class function GetAccessibilityFactory: TVTAccessibilityFactory; static; + procedure RegisterAccessibleProvider(const AProvider: IVTAccessibleProvider); + procedure UnRegisterAccessibleProvider(const AProvider: IVTAccessibleProvider); + end; + + +implementation + +{ TVTAccessibilityFactory } + +constructor TVTAccessibilityFactory.Create; +begin + inherited Create; + FAccessibleProviders := TInterfaceList.Create; + FAccessibleProviders.Clear; +end; + +function TVTAccessibilityFactory.CreateIAccessible(ATree: TCustomControl): IAccessible; +var + I: Integer; + TmpIAccessible: IAccessible; + lTree: TBaseVirtualTree; +// returns an IAccessible. +// 1. If the Accessible property of the passed-in tree is nil, +// the first registered element will be returned. +// Usually, this is the IAccessible that provides information about the tree itself. +// If it is not nil, we'll check whether the AccessibleItem is nil. +// If it is, we'll look in the registered IAccessibles for the appropriate one. +// Each IAccessibleProvider will check the tree for properties to determine whether it is responsible. +// We'll work top to bottom, from the most complicated to the most simple. +// The index for these should all be greater than 0, e g the IAccessible for the tree itself should always be registered first, then any IAccessible items. +begin + Result := nil; + lTree := (ATree as TBaseVirtualTree); + if lTree <> nil then + begin + if lTree.Accessible = nil then + begin + if FAccessibleProviders.Count > 0 then + begin + Result := IVTAccessibleProvider(FAccessibleProviders.Items[0]).CreateIAccessible(lTree); + Exit; + end; + end; + if lTree.AccessibleItem = nil then + begin + if FAccessibleProviders.Count > 0 then + begin + for I := FAccessibleProviders.Count - 1 downto 1 do + begin + TmpIAccessible := IVTAccessibleProvider(FAccessibleProviders.Items[I]).CreateIAccessible(lTree); + if TmpIAccessible <> nil then + begin + Result := TmpIAccessible; + Break; + end; + end; + if TmpIAccessible = nil then + begin + Result := IVTAccessibleProvider(FAccessibleProviders.Items[0]).CreateIAccessible(lTree); + end; + end; + end + else + Result := lTree.AccessibleItem; + end; +end; + +destructor TVTAccessibilityFactory.Destroy; +begin + FAccessibleProviders.Free; + FAccessibleProviders := nil; + inherited Destroy; +end; + +class procedure TVTAccessibilityFactory.FreeFactory; +begin + FVTAccessibleFactory.Free; +end; + +procedure TVTAccessibilityFactory.RegisterAccessibleProvider(const AProvider: IVTAccessibleProvider); +// Ads a provider if it is not already registered +begin + if FAccessibleProviders.IndexOf(AProvider) < 0 then + FAccessibleProviders.Add(AProvider) +end; + +procedure TVTAccessibilityFactory.UnRegisterAccessibleProvider(const AProvider: IVTAccessibleProvider); +// Unregisters/removes an IAccessible provider if it is present +begin + if FAccessibleProviders.IndexOf(AProvider) >= 0 then + FAccessibleProviders.Remove(AProvider); +end; + +class function TVTAccessibilityFactory.GetAccessibilityFactory: TVTAccessibilityFactory; +// Accessibility helper function to create a singleton class that will create or return +// the IAccessible interface for the tree and the focused node. + +begin + // first, check if we've loaded the library already + if not FAccessibilityAvailable then + FAccessibilityAvailable := True; + if FAccessibilityAvailable then + begin + // Check to see if the class has already been created. + if FVTAccessibleFactory = nil then + FVTAccessibleFactory := TVTAccessibilityFactory.Create; + Result := FVTAccessibleFactory; + end + else + Result := nil; +end; + +initialization + +finalization + TVTAccessibilityFactory.FreeFactory; + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.Actions.pas b/components/virtualtreeview/Source/VirtualTrees.Actions.pas index 8e6078107..9f91a9087 100644 --- a/components/virtualtreeview/Source/VirtualTrees.Actions.pas +++ b/components/virtualtreeview/Source/VirtualTrees.Actions.pas @@ -7,7 +7,8 @@ interface System.Actions, Vcl.Controls, Vcl.ActnList, - VirtualTrees; + VirtualTrees.Types, + VirtualTrees.BaseTree; type TVirtualTreeAction = class(TCustomAction) diff --git a/components/virtualtreeview/Source/VirtualTrees.AncestorFMX.pas b/components/virtualtreeview/Source/VirtualTrees.AncestorFMX.pas new file mode 100644 index 000000000..ca82b78e4 --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.AncestorFMX.pas @@ -0,0 +1,309 @@ +unit VirtualTrees.AncestorFMX; + +{$SCOPEDENUMS ON} + +{****************************************************************************************************************} +{ Project : VirtualTrees } +{ } +{ author : Karol Bieniaszewski } +{ year : 2022 } +{ contibutors : } +{****************************************************************************************************************} + +interface + +uses + System.Classes, System.UITypes, + FMX.Graphics, + VirtualTrees.FMX, VirtualTrees.BaseTree; + +const + EVENT_OBJECT_STATECHANGE = $800A; + +type + TVTAncestorFMX = class abstract(TBaseVirtualTree) + protected + procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Single; Y: Single); override; + procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Single; Y: Single); override; + procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override; + + function PrepareDottedBrush(CurrentDottedBrush: TBrush; Bits: Pointer; const BitsLinesCount: Word): TBrush; override; + + function GetClientHeight: Single; override; + function GetClientWidth: Single; override; + function GetClientRect: TRect; override; + + procedure NotifyAccessibleEvent(pEvent: Uint32 = EVENT_OBJECT_STATECHANGE); virtual; + procedure HScrollChangeProc(Sender: TObject); override; + procedure VScrollChangeProc(Sender: TObject); override; + + procedure Resize; override; + //TODO: CopyCutPaste - need to be implemented + { + function PasteFromClipboard(): Boolean; override; + procedure CopyToClipboard(); override; + procedure CutToClipboard(); override; + } + public + constructor Create(AOwner: TComponent); override; + end; + +implementation +uses + System.SysUtils, + FMX.Forms, + VirtualTrees.Header, + VirtualTrees.Types; + +type + TVTHeaderCracker = class(TVTHeader); + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTAncestorFMX.MouseDown(Button: TMouseButton; Shift: TShiftState; X: Single; Y: Single); //wymaga BaseTree +Var MM: TWMMouse; + hInfo: THitInfo; + P: TPoint; + isNC: Boolean; +begin + P.X:= X; + P.Y:= Y; + if ClientRect.Contains(P) then + begin + isNc:= false; + end else + begin + isNC:= true; + P:= ClientToScreen(P); + end; + FillTWMMouse(MM, Button, Shift, P.X, P.Y, isNC, false); + if TVTHeaderCracker(Header).HandleMessage(TMessage(MM)) then + exit;//!!! + + FillTWMMouse(MM, Button, Shift, X, Y, isNC, false); + // get information about the hit + GetHitTestInfoAt(X, Y, True, hInfo); + + HandleMouseDown(MM, hInfo); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTAncestorFMX.MouseUp(Button: TMouseButton; Shift: TShiftState; X: Single; Y: Single); //wymaga BaseTree +Var MM: TWMMouse; + hInfo: THitInfo; + P: TPoint; + isNC: Boolean; +begin + P.X:= X; + P.Y:= Y; + if ClientRect.Contains(P) then + begin + isNc:= false; + end else + begin + isNC:= true; + P:= ClientToScreen(P); + end; + FillTWMMouse(MM, Button, Shift, P.X, P.Y, isNC, true); + if TVTHeaderCracker(Header).HandleMessage(TMessage(MM)) then + exit;//!!! + + FillTWMMouse(MM, Button, Shift, X, Y, isNC, true); + // get information about the hit + GetHitTestInfoAt(X, Y, True, hInfo); + HandleMouseUp(MM, hInfo); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTAncestorFMX.MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); //wymaga BaseTree +Var M: TCMMouseWheel; + P: TPoint; +begin + P:= Screen.MousePos; + if not ClientRect.Contains(P) then + P:= ClientToScreen(P); + + M.Msg:= CM_MOUSEWHEEL; + M.ShiftState:= Shift; + M.WheelDelta:= WheelDelta; + M.XPos:= P.X; + M.YPos:= P.Y; + M.Result:= 0; + CMMouseWheel(M); + Handled:= M.Result<>0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTAncestorFMX.NotifyAccessibleEvent(pEvent: Uint32); +begin + // Currently empty by intention as highly platfrom depedant +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTAncestorFMX.PrepareDottedBrush(CurrentDottedBrush: TBrush; Bits: Pointer; const BitsLinesCount: Word): TBrush; +Var PatternBitmap: TBitmap; + i_bmp, line, bit: Integer; +begin + //FMX pattern brush is different then VCL. Where color is derived from current one... + //We should have 2 brushes 1 for Tree lines 1 for grid lines + //and recreate it every time when color is changing + + CurrentDottedBrush.Free; + FDottedBrushGridLines.Free; + + Result := nil; + for i_bmp:= 1 to 2 do + begin + PatternBitmap := TBitmap.Create(8, BitsLinesCount); + PatternBitmap.Clear(TAlphaColorRec.Null); //fully transparent + PatternBitmap.Canvas.BeginScene; + + PatternBitmap.Map(TMapAccess.Write, BitmapData); + try + { + DestPitch := PixelFormatBytes[PatternBitmap.PixelFormat]; + System.Move(PAlphaColorArray(BitmapData.Data)[0], PAlphaColorArray(Bits)[0], 8 * 4); + } + for line:= 0 to BitsLinesCount-1 do + begin + for bit:= 0 to 7 do + begin + if PWordArray(Bits)^[line] and (1 shl bit)=0 then + BitmapData.SetPixel(bit, line, clWhite) else + begin + if i_bmp=1 then + BitmapData.SetPixel(bit, line, TreeColors.TreeLineColor) else + BitmapData.SetPixel(bit, line, TreeColors.GridLineColor); + end; + end; + end; + finally + PatternBitmap.UnMap(BitmapData); + end; + + PatternBitmap.Canvas.EndScene; + + if i_bmp=1 then + begin + Result := TStrokeBrush.Create(TBrushKind.Bitmap, clWhite); + Result.Bitmap.Bitmap.Assign(PatternBitmap); + end else + begin + FDottedBrushGridLines := TStrokeBrush.Create(TBrushKind.Bitmap, clWhite); + FDottedBrushGridLines.Bitmap.Bitmap.Assign(PatternBitmap); + end; + FreeAndNil(PatternBitmap); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTAncestorFMX.Resize; +Var M: TWMSize; +begin + inherited; + + if FInCreate then + exit; //!! + + M.Msg:= WM_SIZE; + M.SizeType:= SIZE_RESTORED; + M.Width:= Width; + M.Height:= Height; + M.Result:= 0; + WMSize(M); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTAncestorFMX.VScrollChangeProc(Sender: TObject); +Var M: TWMHScroll; +begin + M.Msg:= WM_VSCROLL; + M.ScrollCode:= SB_THUMBPOSITION; + M.Pos:= GetScrollPos(SB_VERT); + M.ScrollBar:= SB_VERT; + M.Result:= 0; + + WMVScroll(M); + Repaint; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTAncestorFMX.HScrollChangeProc(Sender: TObject); +Var M: TWMHScroll; +begin + M.Msg:= WM_HSCROLL; + M.ScrollCode:= SB_THUMBPOSITION; + M.Pos:= GetScrollPos(SB_HORZ); + M.ScrollBar:= SB_HORZ; + M.Result:= 0; + + WMHScroll(M); + Repaint; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +constructor TVTAncestorFMX.Create(AOwner: TComponent); +begin + FInCreate:= true; + + inherited; + + BackgroundOffsetX:= 0; + BackgroundOffsetY:= 0; + Margin:= 4; + TextMargin:= 4; + DefaultNodeHeight:= 18; //??? + Indent:= 18; //??? + + FInCreate:= false; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTAncestorFMX.GetClientHeight: Single; +begin + Result:= ClientRect.Height; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTAncestorFMX.GetClientWidth: Single; +begin + Result:= ClientRect.Width; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTAncestorFMX.GetClientRect: TRect; +begin + Result:= ClipRect; + if Assigned(Header) then + begin + if TVTHeaderOption.hoVisible in Header.Options then + Inc(Result.Top, Header.Height); + end; + if FVScrollBar.Visible then + Dec(Result.Right, VScrollBar.Width); + if HScrollBar.Visible then + Dec(Result.Bottom, HScrollBar.Height); + + if Result.Left>Result.Right then + Result.Left:= Result.Right; + + if Result.Top>Result.Bottom then + Result.Top:= Result.Bottom; + + //OffsetRect(Result, OffsetX, OffsetY); + //Dec(Result.Left, -OffsetX); //increase width + //Dec(Result.Top, -OffsetY); //increase height +end; + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.AncestorVcl.pas b/components/virtualtreeview/Source/VirtualTrees.AncestorVcl.pas new file mode 100644 index 000000000..d0739a9e7 --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.AncestorVcl.pas @@ -0,0 +1,494 @@ +unit VirtualTrees.AncestorVCL; + +{$SCOPEDENUMS ON} + +{****************************************************************************************************************} +{ Project : VirtualTrees } +{ } +{ author : Karol Bieniaszewski, look at VirtualTrees.pas as some code moved from there } +{ year : 2022 } +{ contibutors : } +{****************************************************************************************************************} + +interface + +uses + Vcl.Controls, + Vcl.Themes, + Winapi.Messages, + Winapi.Windows, + Winapi.oleacc, + Winapi.ActiveX, + VirtualTrees.Types, + VirtualTrees.BaseTree; + +type + TVTRenderOLEDataEvent = procedure(Sender: TBaseVirtualTree; const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; + ForClipboard: Boolean; var Result: HRESULT) of object; + + TVTAncestorVcl = class abstract(TBaseVirtualTree) + private + FOnRenderOLEData: TVTRenderOLEDataEvent; // application/descendant defined clipboard formats + + protected + function GetHintWindowClass: THintWindowClass; override; + class function GetTreeFromDataObject(const DataObject: TVTDragDataObject): TBaseVirtualTree; deprecated 'Use class TVTDragManager.GetTreeFromDataObject() instead'; + function DoRenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; ForClipboard: Boolean): HRESULT; override; + property OnRenderOLEData: TVTRenderOLEDataEvent read FOnRenderOLEData write FOnRenderOLEData; + public //methods + function PasteFromClipboard(): Boolean; override; + end; + + // The trees need an own hint window class because of Unicode output and adjusted font. + TVirtualTreeHintWindow = class(THintWindow) + strict private + FHintData: TVTHintData; + FTextHeight: TDimension; + procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED; + procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND; + strict protected + procedure CreateParams(var Params: TCreateParams); override; + procedure Paint; override; + // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) + function StyleServices(AControl: TControl = nil): TCustomStyleServices; + public + function CalcHintRect(MaxWidth: TDimension; const AHint: string; AData: Pointer): TRect; override; + function IsHintMsg(var Msg: TMsg): Boolean; override; + end; + +implementation +uses + System.Classes, + Vcl.Graphics, + System.UITypes, + Vcl.AxCtrls, + Vcl.Forms, + Vcl.GraphUtil, + VirtualTrees.ClipBoard, + VirtualTrees.DataObject, + VirtualTrees.DragnDrop, + VirtualTrees.StyleHooks; + +resourcestring + SClipboardFailed = 'Clipboard operation failed.'; + +type + TBVTCracker = class(TBaseVirtualTree); + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTAncestorVcl.DoRenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; ForClipboard: Boolean): HRESULT; +begin + Result := E_FAIL; + if Assigned(FOnRenderOLEData) then + FOnRenderOLEData(Self, FormatEtcIn, Medium, ForClipboard, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTAncestorVcl.GetHintWindowClass: THintWindowClass; + +// Returns the default hint window class used for the tree. Descendants can override it to use their own classes. + +begin + Result := TVirtualTreeHintWindow; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +class function TVTAncestorVcl.GetTreeFromDataObject(const DataObject: TVTDragDataObject): TBaseVirtualTree; + +// Returns the owner/sender of the given data object by means of a special clipboard format +// or nil if the sender is in another process or no virtual tree at all. + +var + Medium: TStgMedium; + Data: PVTReference; + +begin + Result := nil; + if Assigned(DataObject) then + begin + StandardOLEFormat.cfFormat := CF_VTREFERENCE; + if DataObject.GetData(StandardOLEFormat, Medium) = S_OK then + begin + Data := GlobalLock(Medium.hGlobal); + if Assigned(Data) then + begin + if Data.Process = GetCurrentProcessID then + Result := Data.Tree; + GlobalUnlock(Medium.hGlobal); + end; + ReleaseStgMedium(Medium); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTAncestorVcl.PasteFromClipboard(): Boolean; + +// Reads what is currently on the clipboard into the tree (if the format is supported). +// Note: If the application wants to have text or special formats to be inserted then it must implement +// its own code (OLE). Here only the native tree format is accepted. + +var + Data: IDataObject; + Source: TBaseVirtualTree; + +begin + Result := False; + if not (toReadOnly in TreeOptions.MiscOptions) then + begin + if OleGetClipboard(Data) <> S_OK then + RaiseVTError(SClipboardFailed, hcTFClipboardFailed) + else + begin + // Try to get the source tree of the operation to optimize the operation. + Source := TVTDragManager.GetTreeFromDataObject(Data); + Result := ProcessOLEData(Source, Data, FocusedNode, DefaultPasteMode, Assigned(Source) and + (tsCutPending in Source.TreeStates)); + if Assigned(Source) then + begin + if Source <> Self then + Source.FinishCutOrCopy + else + DoStateChange([], [tsCutPending]); + end; + end; + end; +end; + +//----------------- TVirtualTreeHintWindow ----------------------------------------------------------------------------- + +procedure TVirtualTreeHintWindow.CMTextChanged(var Message: TMessage); + +begin + // swallow this message to prevent the ancestor from resizing the window (we don't use the caption anyway) +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeHintWindow.WMEraseBkgnd(var Message: TWMEraseBkgnd); + +// The control is fully painted by own code so don't erase its background as this causes flickering. + +begin + Message.Result := 1; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeHintWindow.CreateParams(var Params: TCreateParams); + +begin + inherited CreateParams(Params); + + with Params do + begin + Style := WS_POPUP; + ExStyle := ExStyle and not WS_EX_CLIENTEDGE; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeHintWindow.Paint(); +var + R: TRect; + Y: Integer; + S: string; + DrawFormat: Cardinal; + HintKind: TVTHintKind; + LClipRect: TRect; + + LColor: TColor; + LDetails: TThemedElementDetails; + LGradientStart: TColor; + LGradientEnd: TColor; + +begin + with FHintData do + begin + // Do actual painting only in the very first run. + // If the given node is nil then we have to display a header hint. + if (Node = nil) or (TBVTCracker(Tree).HintMode <> hmToolTip) then + begin + Canvas.Font := Screen.HintFont; + Canvas.Font.Height := MulDiv(Canvas.Font.Height, Tree.ScaledPixels(96), Screen.PixelsPerInch); // See issue #992 + Y := 2; + end + else + begin + Tree.GetTextInfo(Node, Column, Canvas.Font, R, S); + if LineBreakStyle = hlbForceMultiLine then + Y := 1 + else + Y := (R.Top - R.Bottom + Self.Height) div 2; + end; + + R := Rect(0, 0, Width, Height); + + HintKind := vhkText; + if Assigned(Node) then + TBVTCracker(Tree).DoGetHintKind(Node, Column, HintKind); + + if HintKind = vhkOwnerDraw then + begin + TBVTCracker(Tree).DoDrawHint(Canvas, Node, R, Column); + end + else + with Canvas do + begin + if TBVTCracker(Tree).VclStyleEnabled then + begin + InflateRect(R, -1, -1); // Fixes missing border when VCL styles are used + LDetails := StyleServices(Tree).GetElementDetails(thHintNormal); + if StyleServices(Tree).GetElementColor(LDetails, ecGradientColor1, LColor) and (LColor <> clNone) then + LGradientStart := LColor + else + LGradientStart := clInfoBk; + if StyleServices(Tree).GetElementColor(LDetails, ecGradientColor2, LColor) and (LColor <> clNone) then + LGradientEnd := LColor + else + LGradientEnd := clInfoBk; + if StyleServices(Tree).GetElementColor(LDetails, ecTextColor, LColor) and (LColor <> clNone) then + Font.Color := LColor + else + Font.Color := Screen.HintFont.Color; + GradientFillCanvas(Canvas, LGradientStart, LGradientEnd, R, gdVertical); + end + else + begin + // Still force tooltip back and text color. + Font.Color := clInfoText; + Pen.Color := clBlack; + Brush.Color := clInfoBk; + if StyleServices(Tree).Enabled and ((toThemeAware in TBVTCracker(Tree).TreeOptions.PaintOptions) or + (toUseExplorerTheme in TBVTCracker(Tree).TreeOptions.PaintOptions)) then + begin + if toUseExplorerTheme in TBVTCracker(Tree).TreeOptions.PaintOptions then // ToolTip style + StyleServices(Tree).DrawElement(Canvas.Handle, StyleServices(Tree).GetElementDetails(tttStandardNormal), R {$IF CompilerVersion >= 34}, nil, FCurrentPPI{$IFEND}) + else + begin // Hint style + LClipRect := R; + InflateRect(R, 4, 4); + StyleServices(Tree).DrawElement(Handle, StyleServices(Tree).GetElementDetails(tttStandardNormal), R, @LClipRect{$IF CompilerVersion >= 34}, FCurrentPPI{$IFEND}); + R := LClipRect; + StyleServices(Tree).DrawEdge(Handle, StyleServices(Tree).GetElementDetails(twWindowRoot), R, [eeRaisedOuter], [efRect]); + end; + end + else + if TBVTCracker(Tree).VclStyleEnabled then + StyleServices(Tree).DrawElement(Canvas.Handle, StyleServices(Tree).GetElementDetails(tttStandardNormal), R {$IF CompilerVersion >= 34}, nil, FCurrentPPI{$IFEND}) + else + Rectangle(R); + end; + // Determine text position and don't forget the border. + InflateRect(R, -1, -1); + DrawFormat := DT_TOP or DT_NOPREFIX; + SetBkMode(Handle, Winapi.Windows.TRANSPARENT); + R.Top := Y; + R.Left := R.Left + 3; // Make the text more centered + if Assigned(Node) and (LineBreakStyle = hlbForceMultiLine) then + DrawFormat := DrawFormat or DT_WORDBREAK; + Winapi.Windows.DrawTextW(Handle, PWideChar(HintText), Length(HintText), R, DrawFormat); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeHintWindow.StyleServices(AControl: TControl): TCustomStyleServices; +begin + Result := VTStyleServices(AControl); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect; + +var + TM: TTextMetric; + R: TRect; + +begin + try + if AData = nil then + // Defensive approach, it *can* happen that AData is nil. Maybe when several user defined hint classes are used. + Result := Rect(0, 0, 0, 0) + else + begin + // The hint window does not need any bidi mode setting but the caller of this method (TApplication.ActivateHint) + // does some unneccessary actions if the hint window is not left-to-right. + // The text alignment is based on the bidi mode passed in the hint data, hence we can + // simply set the window's mode to left-to-right (it might have been modified by the caller, if the + // tree window is right-to-left aligned). + BidiMode := bdLeftToRight; + + FHintData := PVTHintData(AData)^; + + with FHintData do + begin + // The draw tree gets its hint size by the application (but only if not a header hint is about to show). + // If the user will be drawing the hint, it gets its hint size by the application + // (but only if not a header hint is about to show). + // This size has already been determined in CMHintShow. + if Assigned(Node) and (not IsRectEmpty(HintRect)) then + Result := HintRect + else + begin + if Column <= NoColumn then + begin + BidiMode := Tree.BidiMode; + Alignment := TBVTCracker(Tree).Alignment; + end + else + begin + BidiMode := Tree.Header.Columns[Column].BidiMode; + Alignment := Tree.Header.Columns[Column].Alignment; + end; + + if BidiMode <> bdLeftToRight then + ChangeBidiModeAlignment(Alignment); + + if (Node = nil) or (TBVTCracker(Tree).HintMode <> hmToolTip) then + begin + Canvas.Font := Screen.HintFont; + Canvas.Font.Height := MulDiv(Canvas.Font.Height, Tree.ScaledPixels(96), Screen.PixelsPerInch); // See issue #992 + end + else + begin + Canvas.Font := Tree.Font; + with TBVTCracker(Tree) do + DoPaintText(Node, Self.Canvas, Column, ttNormal); + end; + + GetTextMetrics(Canvas.Handle, TM); + FTextHeight := TM.tmHeight; + + if Length(HintText) = 0 then + Result := Rect(0, 0, 0, 0) + else + begin + if Assigned(Node) and (TBVTCracker(Tree).HintMode = hmToolTip) then + begin + // Determine actual line break style depending on what was returned by the methods and what's in the node. + if LineBreakStyle = hlbDefault then + if (vsMultiline in Node.States) or HintText.Contains(#13) then + LineBreakStyle := hlbForceMultiLine + else + LineBreakStyle := hlbForceSingleLine; + + // Hint for a node. + if LineBreakStyle = hlbForceMultiLine then + begin + // Multiline tooltips use the columns width but extend the bottom border to fit the whole caption. + Result := Tree.GetDisplayRect(Node, Column, True, False); + R := Result; + + // On Windows NT/2K/XP the behavior of the tooltip is slightly different to that on Windows 9x/Me. + // We don't have Unicode word wrap on the latter so the tooltip gets as wide as the largest line + // in the caption (limited by carriage return), which results in unoptimal overlay of the tooltip. + Winapi.Windows.DrawTextW(Canvas.Handle, PWideChar(HintText), Length(HintText), R, DT_CALCRECT or DT_WORDBREAK); + if BidiMode = bdLeftToRight then + Result.Right := R.Right + TBVTCracker(Tree).TextMargin + else + Result.Left := R.Left - TBVTCracker(Tree).TextMargin + 1; + Result.Bottom := R.Bottom; + + Inc(Result.Right); + + // If the node height and the column width are both already large enough to cover the entire text, + // then we don't need the hint, though. + // However if the text is partially scrolled out of the client area then a hint is useful as well. + if (Tree.Header.Columns.Count > 0) and ((Tree.NodeHeight[Node] + 2) >= (Result.Bottom - Result.Top)) and + ((Tree.Header.Columns[Column].Width + 2) >= (Result.Right - Result.Left)) and not + ((Result.Left < 0) or (Result.Right > Tree.ClientWidth + 3) or + (Result.Top < 0) or (Result.Bottom > Tree.ClientHeight + 3)) then + begin + Result := Rect(0, 0, 0, 0); + Exit; + end; + end + else + begin + Result := TBVTCracker(Tree).LastHintRect; // = Tree.GetDisplayRect(Node, Column, True, True, True); see TBaseVirtualTree.CMHintShow + + { Fixes issue #623 + + Measure the rectangle to draw the text. The width of the result + is always adjusted according to the hint text because it may + be a custom hint coming in which can be larger or smaller than + the node text. + Earlier logic was using the current width of the node that was + either cutting off the hint text or producing undesired space + on the right. + } + R := Rect(0, 0, MaxWidth, FTextHeight); + Winapi.Windows.DrawTextW(Canvas.Handle, PWideChar(HintText), Length(HintText), R, DT_CALCRECT or DT_TOP or DT_NOPREFIX or DT_WORDBREAK); + if R.Right <> result.right - result.left then + begin + result.Right := result.Left + r.Right; + + //Space on right--taken from the code in the hmHint branch below. + if Assigned(Tree) then + Inc(Result.Right, TBVTCracker(Tree).TextMargin + TBVTCracker(Tree).Margin + Tree.ScaledPixels(4)); + end; + // Fix ends. + + if toShowHorzGridLines in TBVTCracker(Tree).TreeOptions.PaintOptions then + Dec(Result.Bottom); + end; + + // Include a one pixel border. + InflateRect(Result, 1, 1); + + // Make the coordinates relative. They will again be offset by the caller code. + OffsetRect(Result, -Result.Left - 1, -Result.Top - 1); + end + else + begin + // Hint for a header or non-tooltip hint. + + // Start with the base size of the hint in client coordinates. + Result := Rect(0, 0, MaxWidth, FTextHeight); + // Calculate the true size of the text rectangle. + Winapi.Windows.DrawTextW(Canvas.Handle, PWideChar(HintText), Length(HintText), Result, DT_CALCRECT or DT_TOP or DT_NOPREFIX or DT_WORDBREAK); + // The height of the text plus 2 pixels vertical margin plus the border determine the hint window height. + // Minus 4 because THintWindow.ActivateHint adds 4 to Rect.Bottom anyway. Note that it is not scaled because the RTL itself does not do any scaling either. + Inc(Result.Bottom, Tree.ScaledPixels(6) - 4); + // The text is centered horizontally with usual text margin for left and right borders (plus border). + if not Assigned(Tree) then + Exit; // Workaround, because we have seen several exceptions here caught by Eurekalog. Submitted as issue #114 to http://code.google.com/p/virtual-treeview/ + { Issue #623 Fix for strange space on the right. + Original logic was adding FTextHeight. Changed it to add FMargin instead and + it looks OK even if the hint font is larger. + } + Inc(Result.Right, TBVTCracker(Tree).TextMargin + + TBVTCracker(Tree).Margin + Tree.ScaledPixels(4)); //Issue #623 space on right + //+ FTextHeight); // Old code: We are extending the width here, but the text height scales with the text width and has a similar value as AveCharWdith * 2. + end; + end; + end; + end; + end; + except + Application.HandleException(Self); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeHintWindow.IsHintMsg(var Msg: TMsg): Boolean; + +// The VCL is a bit too generous when telling that an existing hint can be cancelled. Need to specify further here. + +begin + Result := inherited IsHintMsg(Msg) and HandleAllocated and IsWindowVisible(Handle); + // Avoid that mouse moves over the non-client area or cursor key presses cancel the current hint. + if Result and ((Msg.Message = WM_NCMOUSEMOVE) or ((Msg.Message >= WM_KEYFIRST) and (Msg.Message <= WM_KEYLAST) and (Msg.wparam in [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT]))) then + Result := False; +end; + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.BaseAncestorFMX.pas b/components/virtualtreeview/Source/VirtualTrees.BaseAncestorFMX.pas new file mode 100644 index 000000000..9d7ba60cf --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.BaseAncestorFMX.pas @@ -0,0 +1,553 @@ +unit VirtualTrees.BaseAncestorFMX; + +{$SCOPEDENUMS ON} + +{****************************************************************************************************************} +{ Project : VirtualTrees } +{ } +{ author : Karol Bieniaszewski } +{ year : 2022 } +{ contibutors : } +{****************************************************************************************************************} + +interface +uses + {$IFDEF MSWINDOWS} + WinApi.Windows, + {$ENDIF} + System.Classes, System.UITypes, + FMX.Objects, FMX.Graphics, FMX.Controls, FMX.StdCtrls, FMX.Forms, FMX.ImgList, + VirtualTrees.Types, VirtualTrees.FMX; + + +type + TVTBaseAncestorFMX = class abstract(TRectangle) + strict private + FFont: TFont; + procedure SetFont(const Value: TFont); + private + FDottedBrushTreeLines: TStrokeBrush; // used to paint dotted lines without special pens + FDottedBrushGridLines: TStrokeBrush; // used to paint dotted lines without special pens + FInCreate: Boolean; + + function GetFillColor: TAlphaColor; + procedure SetFillColor(const Value: TAlphaColor); + protected + FBevelEdges: TBevelEdges; + FBevelInner: TBevelCut; + FBevelOuter: TBevelCut; + FBevelKind: TBevelKind; + FBevelWidth: TBevelWidth; + FBorderWidth: TBorderWidth; + FHandleAllocated: Boolean; + FBiDiMode: TBiDiMode; + FHScrollBar: TScrollBar; + FVScrollBar: TScrollBar; + + FUseRightToLeftAlignment: Boolean; + + procedure SetBevelCut(Index: Integer; const Value: TBevelCut); + procedure SetBevelEdges(const Value: TBevelEdges); + procedure SetBevelKind(const Value: TBevelKind); + procedure SetBevelWidth(const Value: TBevelWidth); + procedure SetBorderWidth(Value: TBorderWidth); + procedure SetBiDiMode(Value: TBiDiMode); + + function GetClientHeight: Single; virtual; abstract; + function GetClientWidth: Single; virtual; abstract; + function GetClientRect: TRect; virtual; abstract; + procedure UpdateStyleElements; virtual; abstract; + + procedure DoStartDrag(var DragObject: TVTDragDataObject); virtual; abstract; + procedure DoEndDrag(Target: TObject; X, Y: TDimension); virtual; abstract; + procedure DragCanceled; virtual; abstract; + + procedure Resize; override; + function CreateSystemImageSet(): TImageList; + procedure SetWindowTheme(const Theme: string); virtual; + + procedure ChangeScale(M, D: Integer{$if CompilerVersion >= 31}; isDpiChange: Boolean{$ifend}); virtual; abstract; + function GetControlsAlignment: TAlignment; virtual; abstract; + function PrepareDottedBrush(CurrentDottedBrush: TBrush; Bits: Pointer; const BitsLinesCount: Word): TBrush; virtual; abstract; + function GetSelectedCount(): Integer; virtual; abstract; + procedure MarkCutCopyNodes; virtual; abstract; + function GetSortedCutCopySet(Resolve: Boolean): TNodeArray; virtual; abstract; + function GetSortedSelection(Resolve: Boolean): TNodeArray; virtual; abstract; + procedure WriteNode(Stream: TStream; Node: PVirtualNode); virtual; abstract; + procedure DoMouseEnter(); reintroduce; overload; virtual; abstract; + procedure DoMouseLeave(); reintroduce; overload; virtual; abstract; + protected //properties + property DottedBrushTreeLines: TStrokeBrush read FDottedBrushTreeLines write FDottedBrushTreeLines; + property DottedBrushGridLines: TStrokeBrush read FDottedBrushGridLines write FDottedBrushGridLines; + public //methods + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + + function ClientToScreen(P: TPoint): TPoint; + function ScreenToClient(P: TPoint): TPoint; + procedure RecreateWnd; + procedure ShowScrollBar(Bar: Integer; AShow: Boolean); + function SetScrollInfo(Bar: Integer; const ScrollInfo: TScrollInfo; Redraw: Boolean): TDimension; + function GetScrollInfo(Bar: Integer; var ScrollInfo: TScrollInfo): Boolean; + function GetScrollPos(Bar: Integer): TDimension; + function GetScrollBarForBar(Bar: Integer): TScrollBar; + procedure HScrollChangeProc(Sender: TObject); virtual; abstract; + procedure VScrollChangeProc(Sender: TObject); virtual; abstract; + + procedure CopyToClipboard; virtual; abstract; + procedure CutToClipboard; virtual; abstract; + function PasteFromClipboard: Boolean; virtual; abstract; + + /// + /// Alias for IsFocused to make same as Vcl Focused + /// + function Focused(): Boolean; inline; + + /// + /// Convert mouse message to TMouseButton + /// Created as method, to be available in whole hierarchy without specifing Unit file name (prevent circular unit ref). + /// + class function KeysToShiftState(Keys: LongInt): TShiftState; static; + + function GetParentForm(Control: TControl; TopForm: Boolean = True): TCustomForm; + + /// + /// Alias for Repaint on FMX to be compatible with VCL + /// + procedure Invalidate(); inline; + /// + /// Alias for Repaint on FMX to be compatible with VCL + /// + function InvalidateRect(lpRect: PRect; bErase: Boolean): Boolean; inline; + /// + /// Alias for Repaint on FMX to be compatible with VCL + /// + function UpdateWindow(): Boolean; inline; + /// + /// Alias for Repaint on FMX to be compatible with VCL + /// + function RedrawWindow(lprcUpdate: PRect; hrgnUpdate: NativeUInt; flags: UINT): Boolean; overload; inline; + /// + /// Alias for Repaint on FMX to be compatible with VCL + /// + function RedrawWindow(const lprcUpdate: TRect; hrgnUpdate: NativeUInt; flags: UINT): Boolean; overload; inline; + + /// + /// Alias for Repaint on FMX to be compatible with VCL + /// + function SendWM_SETREDRAW(Updating: Boolean): NativeUInt; inline; + + /// + /// Simulate Windows GetSystemMetrics + /// + function GetSystemMetrics(nIndex: Integer): Integer; + procedure Sort(Node: PVirtualNode; Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); reintroduce; overload; virtual; abstract; + public //properties + property Font: TFont read FFont write SetFont; + property ClientRect: TRect read GetClientRect; + property ClientWidth: Single read GetClientWidth; + property ClientHeight: Single read GetClientHeight; + property UseRightToLeftAlignment: Boolean read FUseRightToLeftAlignment write FUseRightToLeftAlignment default false; + property BevelEdges: TBevelEdges read FBevelEdges write SetBevelEdges default [TBevelEdge.beLeft, TBevelEdge.beTop, TBevelEdge.beRight, TBevelEdge.beBottom]; + property BevelInner: TBevelCut index 0 read FBevelInner write SetBevelCut default TBevelCut.bvRaised; + property BevelOuter: TBevelCut index 1 read FBevelOuter write SetBevelCut default TBevelCut.bvLowered; + property BevelKind: TBevelKind read FBevelKind write SetBevelKind default TBevelKind.bkNone; + property BevelWidth: TBevelWidth read FBevelWidth write SetBevelWidth default 1; + property BorderWidth: TBorderWidth read FBorderWidth write SetBorderWidth; + property BiDiMode: TBiDiMode read FBiDiMode write SetBiDiMode; + property HScrollBar: TScrollBar read FHScrollBar; + property VScrollBar: TScrollBar read FVScrollBar; + property HandleAllocated: Boolean read FHandleAllocated; + + /// + /// Alias for Fill.Color to make same use as Vcl Color property + /// + property Color: TAlphaColor read GetFillColor write SetFillColor; + end; + +{$IFNDEF MSWINDOWS} +const + { GetSystemMetrics() codes } + SM_CXVSCROLL = 2; + SM_CYHSCROLL = 3; +{$ENDIF} + +implementation +uses FMX.TextLayout, FMX.Utils + {$IFNDEF MSWINDOWS} + , WinApi.Windows + {$ENDIF} + ; + +//-------- TVTBaseAncestorFMX ------------------------------------------------------------------------------------------ + +class function TVTBaseAncestorFMX.KeysToShiftState(Keys: LongInt): TShiftState; +begin + Result := TShiftState(Word(Keys)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.GetFillColor: TAlphaColor; +begin + Result:= Fill.Color; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +constructor TVTBaseAncestorFMX.Create(AOwner: TComponent); +begin + FInCreate:= true; + inherited; + + FHandleAllocated:= true; + FUseRightToLeftAlignment:= false; + FBevelEdges:= [TBevelEdge.beLeft, TBevelEdge.beTop, TBevelEdge.beRight, TBevelEdge.beBottom]; + FBevelInner:= TBevelCut.bvRaised; + FBevelOuter:= TBevelCut.bvLowered; + FBevelKind:= TBevelKind.bkNone; + FBevelWidth:= 1; + FBorderWidth:= 0; + FFont:= TFont.Create; + DisableFocusEffect := True; + CanFocus := True; + AutoCapture := True; + + FHScrollBar:= TScrollBar.Create(Self); + FHScrollBar.Parent:= Self; + FHScrollBar.Orientation:= TOrientation.Horizontal; + FHScrollBar.Align:= TAlignLayout.MostBottom; + FHScrollBar.Visible:= true; + FHScrollBar.OnChange:= HScrollChangeProc; + FHScrollBar.Margins.Right:= FHScrollBar.Height; + + FVScrollBar:= TScrollBar.Create(Self); + FVScrollBar.Parent:= Self; + FVScrollBar.Orientation:= TOrientation.Vertical; + FVScrollBar.Align:= TAlignLayout.MostRight; + FVScrollBar.Visible:= true; + FVScrollBar.OnChange:= VScrollChangeProc; + //FVScrollBar.Margins.Bottom:= FVScrollBar.Width; + + SetAcceptsControls(false); + + FInCreate:= false; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +destructor TVTBaseAncestorFMX.Destroy(); +begin + inherited; + + if FDottedBrushTreeLines <> nil then + FreeAndNil(FDottedBrushTreeLines); + if FDottedBrushGridLines <> nil then + FreeAndNil(FDottedBrushGridLines); + FreeAndNil(FFont); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.SetBevelCut(Index: Integer; const Value: TBevelCut); +begin + case Index of + 0: { BevelInner } + if Value <> FBevelInner then + begin + FBevelInner := Value; + Repaint; + end; + 1: { BevelOuter } + if Value <> FBevelOuter then + begin + FBevelOuter := Value; + Repaint; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.SetBevelEdges(const Value: TBevelEdges); +begin + if Value <> FBevelEdges then + begin + FBevelEdges := Value; + Repaint; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.SetBevelKind(const Value: TBevelKind); +begin + if Value <> FBevelKind then + begin + FBevelKind := Value; + Repaint; + end; +end; +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.SetBevelWidth(const Value: TBevelWidth); +begin + if Value <> FBevelWidth then + begin + FBevelWidth := Value; + Repaint; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.ScreenToClient(P: TPoint): TPoint; + +begin + Result:= AbsoluteToLocal(P); +end; +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.ClientToScreen(P: TPoint): TPoint; +begin + Result:= LocalToAbsolute(P); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.Invalidate(); +begin + Repaint; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.InvalidateRect(lpRect: PRect; bErase: Boolean): Boolean; +begin + Repaint; + Result:= true; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.UpdateWindow(): Boolean; +begin + Repaint; + Result:= true; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.RedrawWindow(lprcUpdate: PRect; hrgnUpdate: NativeUInt; flags: UINT): Boolean; +begin + Repaint; + Result:= true; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.RedrawWindow(const lprcUpdate: TRect; hrgnUpdate: NativeUInt; flags: UINT): Boolean; +begin + Repaint; + Result:= true; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.RecreateWnd(); +begin + Repaint; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.ShowScrollBar(Bar: Integer; AShow: Boolean); +begin + if (Bar=SB_HORZ) or (Bar=SB_BOTH) then + FHScrollBar.Visible:= AShow; + + if (Bar=SB_VERT) or (Bar=SB_BOTH) then + FVScrollBar.Visible:= AShow; + + if FHScrollBar.Visible and FVScrollBar.Visible then + FHScrollBar.Margins.Right:= FHScrollBar.Height else + FHScrollBar.Margins.Right:= 0; + + Repaint; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.SetScrollInfo(Bar: Integer; const ScrollInfo: TScrollInfo; Redraw: Boolean): TDimension; +Var ScrollBar: TScrollBar; +begin + ScrollBar:= GetScrollBarForBar(Bar); + if ScrollBar=nil then + Exit(0); //!!! + + if ScrollInfo.fMask and SIF_PAGE<>0 then + begin + ScrollBar.SmallChange:= ScrollInfo.nPage; + end; + + if ScrollInfo.fMask and SIF_RANGE<>0 then + begin + ScrollBar.Min:= ScrollInfo.nMin; + ScrollBar.Max:= ScrollInfo.nMax; + end; + + if ScrollInfo.fMask and SIF_POS<>0 then + begin + ScrollBar.Value:= ScrollInfo.nPos; + end; + + Result:= ScrollBar.Value; + + Repaint; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.GetScrollInfo(Bar: Integer; var ScrollInfo: TScrollInfo): Boolean; +Var ScrollBar: TScrollBar; +begin + ScrollBar:= GetScrollBarForBar(Bar); + if ScrollBar=nil then + Exit(False); //!!! + + Result:= true; + + ScrollInfo.cbSize:= SizeOf(TScrollInfo); + ScrollInfo.fMask:= SIF_ALL; + + ScrollInfo.nMin:= ScrollBar.Min; + ScrollInfo.nMax:= ScrollBar.Max; + ScrollInfo.nPage:= ScrollBar.SmallChange; + ScrollInfo.nPos:= ScrollBar.Value; + ScrollInfo.nTrackPos:= ScrollBar.Value; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.GetScrollPos(Bar: Integer): TDimension; +Var ScrollInfo: TScrollInfo; +begin + GetScrollInfo(Bar, ScrollInfo); //ignore result + Result:= ScrollInfo.nPos; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.GetScrollBarForBar(Bar: Integer): TScrollBar; +begin + if (Bar=SB_HORZ) then + Result:= FHScrollBar else + if (Bar=SB_VERT) then + Result:= FVScrollBar else + Result:= nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.SetBiDiMode(Value: TBiDiMode); +begin + if FBiDiMode <> Value then + begin + FBiDiMode := Value; + Repaint; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.SetBorderWidth(Value: TBorderWidth); +begin + if FBorderWidth <> Value then + begin + FBorderWidth := Value; + Repaint; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.SetFillColor(const Value: TAlphaColor); +begin + Fill.Color:= Value; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.SetFont(const Value: TFont); +begin + FFont.Assign(Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.Focused(): Boolean; +begin + Result:= IsFocused; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.GetParentForm(Control: TControl; TopForm: Boolean = True): TCustomForm; +begin + Result:= Control.Root.GetObject as TCustomForm; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.SendWM_SETREDRAW(Updating: Boolean): NativeUInt; +begin + Repaint; + Result:= 0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.GetSystemMetrics(nIndex: Integer): Integer; +begin + {$IFDEF MSWINDOWS} + Result:= GetSystemMetrics(nIndex); + {$ELSE} + case nIndex of + SM_CXVSCROLL: Result:= 16; + SM_CYHSCROLL: Result:= 3; + else + raise Exception.Create('Unknown code for GetSystemMetrics: ' + IntToStr(nIndex)); + end; + {$ENDIF} +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.CreateSystemImageSet(): TImageList; +begin + Result:= TImageList.Create(Self); + FillSystemCheckImages(Self, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.SetWindowTheme(const Theme: string); +begin + //nothing +end; +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorFMX.CreateSystemImageSet(): TImageList; +begin + Result:= TImageList.Create(Self); + FillSystemCheckImages(Self, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorFMX.SetWindowTheme(const Theme: string); +begin + //nothing +end; + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.BaseAncestorVcl.pas b/components/virtualtreeview/Source/VirtualTrees.BaseAncestorVcl.pas new file mode 100644 index 000000000..ce624b59e --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.BaseAncestorVcl.pas @@ -0,0 +1,581 @@ +unit VirtualTrees.BaseAncestorVCL; + +{$SCOPEDENUMS ON} + +{****************************************************************************************************************} +{ Project : VirtualTrees } +{ } +{ author : Karol Bieniaszewski, look at VirtualTrees.pas as some code moved from there } +{ year : 2022 } +{ contibutors : } +{****************************************************************************************************************} + +interface +uses + Winapi.Windows, + Winapi.oleacc, + Winapi.ActiveX, + Winapi.Messages, + System.Classes, + Vcl.Controls, + Vcl.Graphics, + Vcl.StdCtrls, + VirtualTrees.Types; + +type + TVTBaseAncestorVcl = class abstract(TCustomControl) + private + // MSAA support + FAccessible: IAccessible; // The IAccessible interface to the window itself. + FAccessibleItem: IAccessible; // The IAccessible to the item that currently has focus. + FAccessibleName: string; // The name the window is given for screen readers. + FDottedBrushTreeLines: TBrush; // used to paint dotted lines without special pens + + procedure WMGetObject(var Message: TMessage); message WM_GETOBJECT; + protected // methods + function DoRenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; ForClipboard: Boolean): HRESULT; virtual; abstract; + function RenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; ForClipboard: Boolean): HResult; virtual; + procedure NotifyAccessibleEvent(pEvent: DWord = EVENT_OBJECT_STATECHANGE); + function PrepareDottedBrush(CurrentDottedBrush: TBrush; Bits: Pointer; const BitsLinesCount: Word): TBrush; virtual; + function CreateSystemImageSet(): TImageList; + procedure SetWindowTheme(const Theme: string); virtual; + //// Abtract method that are implemented in TBaseVirtualTree, keep in sync with TVTBaseAncestorFMX + function GetSelectedCount(): Integer; virtual; abstract; + procedure MarkCutCopyNodes; virtual; abstract; + procedure DoStateChange(Enter: TVirtualTreeStates; Leave: TVirtualTreeStates = []); virtual; abstract; + function GetSortedCutCopySet(Resolve: Boolean): TNodeArray; virtual; abstract; + function GetSortedSelection(Resolve: Boolean): TNodeArray; virtual; abstract; + procedure WriteNode(Stream: TStream; Node: PVirtualNode); virtual; abstract; + procedure Sort(Node: PVirtualNode; Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); virtual; abstract; + procedure DoMouseEnter(); virtual; abstract; + procedure DoMouseLeave(); virtual; abstract; + protected //properties + property DottedBrushTreeLines: TBrush read FDottedBrushTreeLines write FDottedBrushTreeLines; + public // methods + destructor Destroy; override; + procedure CopyToClipboard(); virtual; + procedure CutToClipboard(); virtual; + function PasteFromClipboard: Boolean; virtual; abstract; + + /// + /// Handle less alias for WinApi.Windows.InvalidateRect + /// + function InvalidateRect(lpRect: PRect; bErase: BOOL): BOOL; inline; + /// + /// Handle less alias for WinApi.Windows.UpdateWindow + /// + function UpdateWindow(): BOOL; inline; + /// + /// Handle less alias for WinApi.Windows.RedrawWindow + /// + function RedrawWindow(lprcUpdate: PRect; hrgnUpdate: HRGN; flags: UINT): BOOL; overload; inline; + /// + /// Handle less alias for WinApi.Windows.RedrawWindow + /// + function RedrawWindow(const lprcUpdate: TRect; hrgnUpdate: HRGN; flags: UINT): BOOL; overload; inline; + + /// + /// Handle less and with limited parameters version + /// + function SendWM_SETREDRAW(Updating: Boolean): LRESULT; inline; + + /// + /// Handle less alias for WinApi.Windows.ShowScrollBar + /// + procedure ShowScrollBar(Bar: Integer; AShow: Boolean); + /// + /// Handle less alias for WinApi.Windows.SetScrollInfo + /// + function SetScrollInfo(Bar: Integer; const ScrollInfo: TScrollInfo; Redraw: Boolean): TDimension; + /// + /// Handle less alias for WinApi.Windows.GetScrollInfo + /// + function GetScrollInfo(Bar: Integer; var ScrollInfo: TScrollInfo): Boolean; + /// + /// Handle less alias for WinApi.Windows.GetScrollPos + /// + function GetScrollPos(Bar: Integer): TDimension; + /// + /// Canvas based without HDC alias for WinApi.Windows.GetTextMetrics + /// + function GetTextMetrics(Canvas: TCanvas; var TM: TTextMetric): BOOL; overload; inline; + public //properties + property Accessible: IAccessible read FAccessible write FAccessible; + property AccessibleItem: IAccessible read FAccessibleItem write FAccessibleItem; + property AccessibleName: string read FAccessibleName write FAccessibleName; + end; + +implementation + +uses + System.SyncObjs, + System.SysUtils, + Vcl.AxCtrls, + Vcl.Forms, + Vcl.Themes, + Winapi.CommCtrl, + Winapi.ShlObj, + Winapi.UxTheme, + VirtualTrees.DataObject, + VirtualTrees.Clipboard, + VirtualTrees.AccessibilityFactory, + VirtualTrees.StyleHooks; + +//---------------------------------------------------------------------------------------------------------------------- + +const + Grays: array[0..3] of TColor = (clWhite, clSilver, clGray, clBlack); + SysGrays: array[0..3] of TColor = (clWindow, clBtnFace, clBtnShadow, clBtnText); + +//not used curently anywhere, moved to VCL, to remove ifdef (gWatcher is declared in VirtualTrees.BaseTree) +procedure ConvertImageList(gWatcher: TCriticalSection; BaseVirtualTreeClass: TClass; IL: TImageList; const ImageName: string; ColorRemapping: Boolean = True); + +// Loads a bunch of images given by ImageName into IL. If ColorRemapping = True then a mapping of gray values to +// system colors is performed. + +var + lImages, + lOneImage: TBitmap; + I: Integer; + MaskColor: TColor; + Source, + Dest: TRect; + +begin + gWatcher.Enter(); + try + // Since we want the image list appearing in the correct system colors, we have to remap its colors. + lImages := TBitmap.Create; + lOneImage := TBitmap.Create; + if ColorRemapping then + lImages.Handle := CreateMappedRes(FindClassHInstance(BaseVirtualTreeClass), PChar(ImageName), Grays, SysGrays) + else + lImages.Handle := LoadBitmap(FindClassHInstance(BaseVirtualTreeClass), PChar(ImageName)); + + try + Assert(lImages.Height > 0, 'Internal image "' + ImageName + '" is missing or corrupt.'); + if lImages.Height = 0 then + Exit;// This should never happen, it prevents a division by zero exception below in the for loop, which we have seen in a few cases + // It is assumed that the image height determines also the width of one entry in the image list. + IL.Clear; + IL.Height := lImages.Height; + IL.Width := lImages.Height; + lOneImage.Width := IL.Width; + lOneImage.Height := IL.Height; + MaskColor := lImages.Canvas.Pixels[0, 0]; // this is usually clFuchsia + Dest := Rect(0, 0, IL.Width, IL.Height); + for I := 0 to (lImages.Width div lImages.Height) - 1 do + begin + Source := Rect(I * IL.Width, 0, (I + 1) * IL.Width, IL.Height); + lOneImage.Canvas.CopyRect(Dest, lImages.Canvas, Source); + IL.AddMasked(lOneImage, MaskColor); + end; + finally + lImages.Free; + lOneImage.Free; + end; + finally + gWatcher.Leave(); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.RenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; ForClipboard: Boolean): HResult; + +// Returns a memory expression of all currently selected nodes in the Medium structure. +// Note: The memory requirement of this method might be very high. This depends however on the requested storage format. +// For HGlobal (a global memory block) we need to render first all nodes to local memory and copy this then to +// the global memory in Medium. This is necessary because we have first to determine how much +// memory is needed before we can allocate it. Hence for a short moment we need twice the space as used by the +// nodes alone (plus the amount the nodes need in the tree anyway)! +// With IStream this does not happen. We directly stream out the nodes and pass the constructed stream along. + + //--------------- local function -------------------------------------------- + + procedure WriteNodes(Stream: TStream); + + var + Selection: TNodeArray; + I: Integer; + + begin + if ForClipboard then + Selection := GetSortedCutCopySet(True) + else + Selection := GetSortedSelection(True); + for I := 0 to High(Selection) do + WriteNode(Stream, Selection[I]); + end; + + //--------------- end local function ---------------------------------------- + +var + Data: PCardinal; + ResPointer: Pointer; + ResSize: Integer; + OLEStream: IStream; + VCLStream: TStream; + +begin + ZeroMemory (@Medium, SizeOf(Medium)); + + // We can render the native clipboard format in two different storage media. + if (FormatEtcIn.cfFormat = CF_VIRTUALTREE) and (FormatEtcIn.tymed and (TYMED_HGLOBAL or TYMED_ISTREAM) <> 0) then + begin + VCLStream := nil; + try + Medium.unkForRelease := nil; + // Return data in one of the supported storage formats, prefer IStream. + if FormatEtcIn.tymed and TYMED_ISTREAM <> 0 then + begin + // Create an IStream on a memory handle (here it is 0 which indicates to implicitely allocated a handle). + // Do not use TStreamAdapter as it is not compatible with OLE (when flushing the clipboard OLE wants the HGlobal + // back which is not supported by TStreamAdapater). + CreateStreamOnHGlobal(0, True, OLEStream); + VCLStream := TOLEStream.Create(OLEStream); + WriteNodes(VCLStream); + // Rewind stream. + VCLStream.Position := 0; + Medium.tymed := TYMED_ISTREAM; + IUnknown(Medium.stm) := OLEStream; + Result := S_OK; + end + else + begin + VCLStream := TMemoryStream.Create; + WriteNodes(VCLStream); + ResPointer := TMemoryStream(VCLStream).Memory; + ResSize := VCLStream.Position; + + // Allocate memory to hold the string. + if ResSize > 0 then + begin + Medium.hGlobal := GlobalAlloc(GHND or GMEM_SHARE, ResSize + SizeOf(Cardinal)); + Data := GlobalLock(Medium.hGlobal); + // Store the size of the data too, for easy retrival. + Data^ := ResSize; + Inc(Data); + Move(ResPointer^, Data^, ResSize); + GlobalUnlock(Medium.hGlobal); + Medium.tymed := TYMED_HGLOBAL; + + Result := S_OK; + end + else + Result := E_FAIL; + end; + finally + // We can free the VCL stream here since it was either a pure memory stream or only a wrapper around + // the OLEStream which exists independently. + VCLStream.Free; + end; + end + else // Ask application descendants to render self defined formats. + Result := DoRenderOLEData(FormatEtcIn, Medium, ForClipboard); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorVcl.CopyToClipboard; + +var + lDataObject: IDataObject; + +begin + if GetSelectedCount > 0 then + begin + lDataObject := TVTDataObject.Create(Self, True); + if OleSetClipboard(lDataObject) = S_OK then + begin + MarkCutCopyNodes; + DoStateChange([tsCopyPending]); + Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.CreateSystemImageSet: TImageList; + +// Creates a system check image set. +// Note: the DarkCheckImages and FlatImages image lists must already be filled, as some images from them are copied here. + +const + MaskColor: TColor = clRed; + cFlags = ILC_COLOR32 or ILC_MASK; + +var + BM: TBitmap; + Theme: HTHEME; + Details: TThemedElementDetails; + + //--------------------------------------------------------------------------- + + // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) + function StyleServices: TCustomStyleServices; + begin + Result := VTStyleServices(Self); + end; + + procedure AddSystemImage(IL: TImageList; Index: Integer); + const + States: array [0..19] of Integer = ( + RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDDISABLED, + RBS_CHECKEDNORMAL, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDDISABLED, + CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDDISABLED, + CBS_CHECKEDNORMAL, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDDISABLED, + CBS_MIXEDNORMAL, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDDISABLED); + var + ButtonState: Cardinal; + ButtonType: Cardinal; + + begin + BM.Canvas.FillRect(Rect(0, 0, BM.Width, BM.Height)); + if StyleServices.Enabled and StyleServices.IsSystemStyle then + begin + if Index < 8 then + Details.Part := BP_RADIOBUTTON + else + Details.Part := BP_CHECKBOX; + Details.State := States[Index]; + DrawThemeBackground(Theme, BM.Canvas.Handle, Details.Part, Details.State, Rect(0, 0, BM.Width, BM.Height), nil); + end + else + begin + if Index < 8 then + ButtonType := DFCS_BUTTONRADIO + else + ButtonType := DFCS_BUTTONCHECK; + if Index >= 16 then + ButtonType := ButtonType or DFCS_BUTTON3STATE; + + case Index mod 4 of + 0: + ButtonState := 0; + 1: + ButtonState := DFCS_HOT; + 2: + ButtonState := DFCS_PUSHED; + else + ButtonState := DFCS_INACTIVE; + end; + if Index in [4..7, 12..19] then + ButtonState := ButtonState or DFCS_CHECKED; +// if Flat then +// ButtonState := ButtonState or DFCS_FLAT; + DrawFrameControl(BM.Canvas.Handle, Rect(0, 0, BM.Width, BM.Height), DFC_BUTTON, ButtonType or ButtonState); + end; + IL.AddMasked(BM, MaskColor); + end; + + //--------------- end local functions --------------------------------------- + +const + cDefaultCheckboxSize = 13;// Used when no other value is available +var + I: Integer; + lSize: TSize; + Res: Boolean; +begin + BM := TBitmap.Create; // Create a temporary bitmap, which holds the intermediate images. + try + Res := False; + // Retrieve the checkbox image size, prefer theme if available, fall back to GetSystemMetrics() otherwise, but this returns odd results on Windows 8 and higher in high-dpi scenarios. + if StyleServices.Enabled then + if StyleServices.IsSystemStyle then + begin + {$if CompilerVersion >= 33} + if TOSVersion.Check(10) and (TOSVersion.Build >= 15063) then + Theme := OpenThemeDataForDPI(Handle, 'BUTTON', CurrentPPI) + else + {$ifend} + Theme := OpenThemeData(Self.Handle, 'BUTTON'); + Details := StyleServices.GetElementDetails(tbCheckBoxUncheckedNormal); + Res := GetThemePartSize(Theme, BM.Canvas.Handle, Details.Part, Details.State, nil, TS_TRUE, lSize) = S_OK; + end + else + Res := StyleServices.GetElementSize(BM.Canvas.Handle, StyleServices.GetElementDetails(tbCheckBoxUncheckedNormal), TElementSize.esActual, lSize {$IF CompilerVersion >= 34}, Self.CurrentPPI{$IFEND}); + if not Res then begin + lSize := TSize.Create(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK)); + if lSize.cx = 0 then begin // error? (Should happen rarely only) + lSize.cx := MulDiv(cDefaultCheckboxSize, Screen.PixelsPerInch, USER_DEFAULT_SCREEN_DPI); + lSize.cy := lSize.cx; + end;// if + end;//if + + Result := TImageList.CreateSize(lSize.cx, lSize.cy); + Result.Handle := ImageList_Create(Result.Width, Result.Height, cFlags, 0, Result.AllocBy); + Result.Masked := True; + Result.BkColor := clWhite; + + // Make the bitmap the same size as the image list is to avoid problems when adding. + BM.SetSize(Result.Width, Result.Height); + BM.Canvas.Brush.Color := MaskColor; + BM.Canvas.Brush.Style := bsSolid; + BM.Canvas.FillRect(Rect(0, 0, BM.Width, BM.Height)); + Result.AddMasked(BM, MaskColor); + + // Add the 20 system checkbox and radiobutton images. + for I := 0 to 19 do + AddSystemImage(Result, I); + if StyleServices.Enabled and StyleServices.IsSystemStyle then + CloseThemeData(Theme); + + finally + BM.Free; + end; +end; + +procedure TVTBaseAncestorVcl.CutToClipboard; +var + lDataObject: IDataObject; +begin + if (GetSelectedCount > 0) then + begin + lDataObject := TVTDataObject.Create(Self, True); + if OleSetClipboard(lDataObject) = S_OK then + begin + MarkCutCopyNodes; + DoStateChange([tsCutPending], [tsCopyPending]); + Invalidate; + end; + end; +end; + +destructor TVTBaseAncestorVcl.Destroy; +begin + // Disconnect all remote MSAA connections + if Assigned(AccessibleItem) then begin + CoDisconnectObject(AccessibleItem, 0); + AccessibleItem := nil; + end; + if Assigned(Accessible) then begin + CoDisconnectObject(Accessible, 0); + Accessible := nil; + end; + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.PrepareDottedBrush(CurrentDottedBrush: TBrush; Bits: Pointer; const BitsLinesCount: Word): TBrush; +begin + if Assigned(CurrentDottedBrush) then + begin + Result := CurrentDottedBrush; + end else + begin + Result := TBrush.Create; + Result.Bitmap := TBitmap.Create; + end; + + Result.Bitmap.Handle := CreateBitmap(8, 8, 1, 1, Bits); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.RedrawWindow(const lprcUpdate: TRect; hrgnUpdate: HRGN; flags: UINT): BOOL; +begin + Result:= Winapi.Windows.RedrawWindow(Handle, lprcUpdate, hrgnUpdate, flags); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.RedrawWindow(lprcUpdate: PRect; hrgnUpdate: HRGN; flags: UINT): BOOL; +begin + Result:= Winapi.Windows.RedrawWindow(Handle, lprcUpdate, hrgnUpdate, flags); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.InvalidateRect(lpRect: PRect; bErase: BOOL): BOOL; +begin + Result:= WinApi.Windows.InvalidateRect(Handle, lpRect, bErase); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorVcl.NotifyAccessibleEvent(pEvent: DWord = EVENT_OBJECT_STATECHANGE); +begin + if Assigned(AccessibleItem) then + NotifyWinEvent(pEvent, Handle, OBJID_CLIENT, CHILDID_SELF); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.UpdateWindow(): BOOL; +begin + Result:= WinApi.Windows.UpdateWindow(Handle); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorVcl.WMGetObject(var Message: TMessage); + +begin + if TVTAccessibilityFactory.GetAccessibilityFactory <> nil then + begin + // Create the IAccessibles for the tree view and tree view items, if necessary. + if Accessible = nil then + Accessible := TVTAccessibilityFactory.GetAccessibilityFactory.CreateIAccessible(Self); + if AccessibleItem = nil then + AccessibleItem := TVTAccessibilityFactory.GetAccessibilityFactory.CreateIAccessible(Self); + if Cardinal(Message.LParam) = OBJID_CLIENT then + if Assigned(Accessible) then + Message.Result := LresultFromObject(IID_IAccessible, Message.WParam, Accessible) + else + Message.Result := 0; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorVcl.ShowScrollBar(Bar: Integer; AShow: Boolean); +begin + WinApi.Windows.ShowScrollBar(Handle, Bar, AShow); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.SendWM_SETREDRAW(Updating: Boolean): LRESULT; +begin + Result:= SendMessage(Handle, WM_SETREDRAW, Ord(not Updating), 0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.SetScrollInfo(Bar: Integer; const ScrollInfo: TScrollInfo; Redraw: Boolean): TDimension; +begin + Result:= WinApi.Windows.SetScrollInfo(Handle, Bar, ScrollInfo, Redraw); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTBaseAncestorVcl.SetWindowTheme(const Theme: string); +begin + Winapi.UxTheme.SetWindowTheme(Handle, PWideChar(Theme), nil); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.GetScrollInfo(Bar: Integer; var ScrollInfo: TScrollInfo): Boolean; +begin + Result:= WinApi.Windows.GetScrollInfo(Handle, Bar, ScrollInfo); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTBaseAncestorVcl.GetScrollPos(Bar: Integer): TDimension; +begin + Result:= WinApi.Windows.GetScrollPos(Handle, Bar); +end; + +function TVTBaseAncestorVcl.GetTextMetrics(Canvas: TCanvas; var TM: TTextMetric): BOOL; +begin + Result:= WinApi.Windows.GetTextMetrics(Canvas.Handle, TM); +end; + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.BaseTree.pas b/components/virtualtreeview/Source/VirtualTrees.BaseTree.pas new file mode 100644 index 000000000..8697a7b58 --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.BaseTree.pas @@ -0,0 +1,22658 @@ +unit VirtualTrees.BaseTree; + +interface + +{$if CompilerVersion < 24}{$MESSAGE FATAL 'This version supports only RAD Studio XE3 and higher. Please use V5 from http://www.jam-software.com/virtual-treeview/VirtualTreeViewV5.5.3.zip or https://github.com/Virtual-TreeView/Virtual-TreeView/archive/V5_stable.zip'}{$ifend} + +{$booleval off} // Use fastest possible boolean evaluation + +// For some things to work we need code, which is classified as being unsafe for .NET. +{$WARN UNSAFE_TYPE OFF} +{$WARN UNSAFE_CAST OFF} +{$WARN UNSAFE_CODE OFF} + +{$LEGACYIFEND ON} +{$WARN UNSUPPORTED_CONSTRUCT OFF} + +{$HPPEMIT '#include '} +{$HPPEMIT '#include '} +{$HPPEMIT '#include '} +{$ifdef BCB} + {$HPPEMIT '#pragma comment(lib, "VirtualTreesCR")'} +{$else} + {$HPPEMIT '#pragma comment(lib, "VirtualTreesR")'} +{$endif} +{$HPPEMIT '#pragma comment(lib, "Shell32")'} +{$HPPEMIT '#pragma comment(lib, "uxtheme")'} +{$HPPEMIT '#pragma link "VirtualTrees.Accessibility"'} + +uses + Winapi.Windows, Winapi.Messages, Winapi.ActiveX, Winapi.CommCtrl, + Winapi.UxTheme, Winapi.ShlObj, + System.SysUtils, System.Classes, System.Types, + Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.ImgList, Vcl.StdCtrls, + Vcl.Menus, Vcl.Printers, Vcl.Themes, + System.UITypes, // some types moved from Vcl.* to System.UITypes + VirtualTrees.Types, + VirtualTrees.Colors, + VirtualTrees.DragImage, + VirtualTrees.Header +{$IFDEF VT_FMX} + , VirtualTrees.BaseAncestorFMX +{$ELSE} + , VirtualTrees.BaseAncestorVCL +{$ENDIF} + ; + +{$MinEnumSize 1, make enumerations as small as possible} + +type + {$IFDEF VT_FMX} + TVTBaseAncestor = TVTBaseAncestorFMX; + TCanvas = FMX.Graphics.TCanvas; + {$ELSE} + TVTBaseAncestor = TVTBaseAncestorVcl; + TCanvas = Vcl.Graphics.TCanvas; + TFormatEtcArray = VirtualTrees.Types.TFormatEtcArray; + {$ENDIF} + + // Alias defintions for convenience + TImageIndex = System.UITypes.TImageIndex; + + //these were moved, aliases are for backwards compatibility. + //some may be removed once we sort out excactly what is needed. + + TDimension = VirtualTrees.Types.TDimension; + TColumnIndex = VirtualTrees.Types.TColumnIndex; + TColumnPosition = VirtualTrees.Types.TColumnPosition; + EVirtualTreeError = VirtualTrees.Types.EVirtualTreeError; + TAutoScrollInterval = VirtualTrees.Types.TAutoScrollInterval; + TVTScrollIncrement = VirtualTrees.Types.TVTScrollIncrement; + TFormatArray = VirtualTrees.Types.TFormatArray; + + TVTPaintOption = VirtualTrees.Types.TVTPaintOption; + TVTPaintOptions = VirtualTrees.Types.TVTPaintOptions; + TVTAnimateOption = VirtualTrees.Types.TVTAnimationOption; + TVTAnimateOptions = VirtualTrees.Types.TVTAnimationOptions; + TVTAutoOption = VirtualTrees.Types.TVTAutoOption; + TVTAutoOptions = VirtualTrees.Types.TVTAutoOptions; + TVTSelectionOption = VirtualTrees.Types.TVTSelectionOption; + TVTSelectionOptions = VirtualTrees.Types.TVTSelectionOptions; + TVTEditOptions = VirtualTrees.Types.TVTEditOptions; + TVTMiscOption = VirtualTrees.Types.TVTMiscOption; + TVTMiscOptions = VirtualTrees.Types.TVTMiscOptions; + TVTExportMode = VirtualTrees.Types.TVTExportMode; + TVTStringOption = VirtualTrees.Types.TVTStringOption; + TVTStringOptions = VirtualTrees.Types.TVTStringOptions; + TCustomVirtualTreeOptions= VirtualTrees.Types.TCustomVirtualTreeOptions; + TVirtualTreeOptions = VirtualTrees.Types.TVirtualTreeOptions; + TTreeOptionsClass = VirtualTrees.Types.TTreeOptionsClass; + TCustomStringTreeOptions = VirtualTrees.Types.TCustomStringTreeOptions; + TStringTreeOptions = VirtualTrees.Types.TStringTreeOptions; + + TScrollBarStyle = VirtualTrees.Types.TScrollBarStyle; + TScrollBarOptions = VirtualTrees.Types.TScrollBarOptions; + + TVTColumnOption = VirtualTrees.Types.TVTColumnOption; + TVTColumnOptions = VirtualTrees.Types.TVTColumnOptions; + TVirtualTreeColumnStyle = VirtualTrees.Types.TVirtualTreeColumnStyle; + TSortDirection = VirtualTrees.Types.TSortDirection; + TCheckType = VirtualTrees.Types.TCheckType; + TCheckState = VirtualTrees.Types.TCheckState; + TVTDropMarkMode = VirtualTrees.Types.TVTDropMarkMode; + TScrollDirections = VirtualTrees.Types.TScrollDirections; + TVirtualTreeColumn = VirtualTrees.Header.TVirtualTreeColumn; + TVirtualTreeColumns = VirtualTrees.Header.TVirtualTreeColumns; + TVirtualTreeColumnClass = VirtualTrees.Header.TVirtualTreeColumnClass; + TColumnsArray = VirtualTrees.Header.TColumnsArray; + TCardinalArray = VirtualTrees.Header.TCardinalArray; + TIndexArray = VirtualTrees.Header.TIndexArray; + + TVTColors = VirtualTrees.Colors.TVTColors; + // + +type + TBaseVirtualTree = class; + TVirtualTreeClass = class of TBaseVirtualTree; + + + // This record must already be defined here and not later because otherwise BCB users will not be able + // to compile (conversion done by BCB is wrong). + TCacheEntry = record + Node: PVirtualNode; + AbsoluteTop: TNodeHeight; + end; + + TCache = array of TCacheEntry; + + // Used in the CF_VTREFERENCE clipboard format. + PVTReference = ^TVTReference; + TVTReference = record + Process: Cardinal; + Tree: TBaseVirtualTree; + end; + + + // ----- OLE drag'n drop handling + + IVTDragManager = interface(IUnknown) + ['{C4B25559-14DA-446B-8901-0C879000EB16}'] + procedure ForceDragLeave; stdcall; + function GetDataObject: IDataObject; stdcall; + function GetDragSource: TBaseVirtualTree; stdcall; + function GetIsDropTarget: Boolean; stdcall; + + property DataObject: IDataObject read GetDataObject; + property DragSource: TBaseVirtualTree read GetDragSource; + property IsDropTarget: Boolean read GetIsDropTarget; + end; + + + + PVTHintData = ^TVTHintData; + TVTHintData = record + Tree: TBaseVirtualTree; + Node: PVirtualNode; + Column: TColumnIndex; + HintRect: TRect; // used for draw trees only, string trees get the size from the hint string + HintText: string; // set when size of the hint window is calculated + BidiMode: TBidiMode; + Alignment: TAlignment; + LineBreakStyle: TVTToolTipLineBreakStyle; + end; + + // Communication interface between a tree editor and the tree itself (declared as using stdcall in case it + // is implemented in a (C/C++) DLL). The GUID is not nessecary in Delphi but important for BCB users + // to allow QueryInterface and _uuidof calls. + IVTEditLink = interface + ['{2BE3EAFA-5ACB-45B4-9D9A-B58BCC496E17}'] + function BeginEdit: Boolean; stdcall; // Called when editing actually starts. + function CancelEdit: Boolean; stdcall; // Called when editing has been cancelled by the tree. + function EndEdit: Boolean; stdcall; // Called when editing has been finished by the tree. Returns True if successful, False if edit mode is still active. + function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall; + // Called after creation to allow a setup. + procedure ProcessMessage(var Message: TMessage); stdcall; + // Used to forward messages to the edit window(s)- + procedure SetBounds(R: TRect); stdcall; // Called to place the editor. + end; + + + TVTNodeExportEvent = procedure (Sender: TBaseVirtualTree; aExportType: TVTExportType; Node: PVirtualNode) of object; + TVTColumnExportEvent = procedure (Sender: TBaseVirtualTree; aExportType: TVTExportType; Column: TVirtualTreeColumn) of object; + TVTTreeExportEvent = procedure(Sender: TBaseVirtualTree; aExportType: TVTExportType) of object; + + TClipboardFormats = class(TStringList) + private + FOwner: TBaseVirtualTree; + public + constructor Create(AOwner: TBaseVirtualTree); virtual; + + function Add(const S: string): Integer; override; + procedure Insert(Index: Integer; const S: string); override; + property Owner: TBaseVirtualTree read FOwner; + end; + + // ----- Event prototypes: + + // node enumeration + TVTGetNodeProc = reference to procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean); + // node events + TVTChangingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; var Allowed: Boolean) of object; + TVTCheckChangingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; var NewState: TCheckState; + var Allowed: Boolean) of object; + TVTChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; + TVTStructureChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Reason: TChangeReason) of object; + TVTEditCancelEvent = procedure(Sender: TBaseVirtualTree; Column: TColumnIndex) of object; + TVTEditChangingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; + var Allowed: Boolean) of object; + TVTEditChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex) of object; + TVTFreeNodeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; + TVTFocusChangingEvent = procedure(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode; OldColumn, + NewColumn: TColumnIndex; var Allowed: Boolean) of object; + TVTFocusChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex) of object; + TVTAddToSelectionEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; + TVTRemoveFromSelectionEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; + TVTGetImageEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; + var Ghosted: Boolean; var ImageIndex: TImageIndex) of object; + TVTGetImageExEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; + var Ghosted: Boolean; var ImageIndex: TImageIndex; var ImageList: TCustomImageList) of object; + TVTGetImageTextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; + var ImageText: string) of object; + TVTHotNodeChangeEvent = procedure(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode) of object; + TVTInitChildrenEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; var ChildCount: Cardinal) of object; + TVTInitNodeEvent = procedure(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; + var InitialStates: TVirtualNodeInitStates) of object; + TVTPopupEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; const P: TPoint; + var AskParent: Boolean; var PopupMenu: TPopupMenu) of object; + TVTHelpContextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; + var HelpContext: Integer) of object; + TVTCreateEditorEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; + out EditLink: IVTEditLink) of object; + TVTSaveTreeEvent = procedure(Sender: TBaseVirtualTree; Stream: TStream) of object; + TVTSaveNodeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Stream: TStream) of object; + TVTBeforeGetCheckStateEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; + + // header/column events + TVTHeaderAddPopupItemEvent = procedure(const Sender: TObject; const Column: TColumnIndex; var Cmd: TAddPopupItemType) of object; + TVTHeaderClickEvent = procedure(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo) of object; + TVTHeaderMouseEvent = procedure(Sender: TVTHeader; Button: TMouseButton; Shift: TShiftState; X, Y: TDimension) of object; + TVTHeaderMouseMoveEvent = procedure(Sender: TVTHeader; Shift: TShiftState; X, Y: TDimension) of object; + TVTBeforeHeaderHeightTrackingEvent = procedure(Sender: TVTHeader; Shift: TShiftState) of object; + TVTAfterHeaderHeightTrackingEvent = procedure(Sender: TVTHeader) of object; + TVTHeaderHeightTrackingEvent = procedure(Sender: TVTHeader; var P: TPoint; Shift: TShiftState; var Allowed: Boolean) of object; + TVTHeaderHeightDblClickResizeEvent = procedure(Sender: TVTHeader; var P: TPoint; Shift: TShiftState; var Allowed: Boolean) of object; + TVTHeaderNotifyEvent = procedure(Sender: TVTHeader; Column: TColumnIndex) of object; + TVTHeaderDraggingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var Allowed: Boolean) of object; + TVTHeaderDraggedEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; OldPosition: Integer) of object; + TVTHeaderDraggedOutEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; DropPosition: TPoint) of object; + TVTHeaderPaintEvent = procedure(Sender: TVTHeader; HeaderCanvas: TCanvas; Column: TVirtualTreeColumn; R: TRect; Hover, + Pressed: Boolean; DropMark: TVTDropMarkMode) of object; + TVTHeaderPaintQueryElementsEvent = procedure(Sender: TVTHeader; var PaintInfo: THeaderPaintInfo; + var Elements: THeaderPaintElements) of object; + TVTAdvancedHeaderPaintEvent = procedure(Sender: TVTHeader; var PaintInfo: THeaderPaintInfo; + const Elements: THeaderPaintElements) of object; + TVTBeforeAutoFitColumnsEvent = procedure(Sender: TVTHeader; var SmartAutoFitType: TSmartAutoFitType) of object; + TVTBeforeAutoFitColumnEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var SmartAutoFitType: TSmartAutoFitType; + var Allowed: Boolean) of object; + TVTAfterAutoFitColumnEvent = procedure(Sender: TVTHeader; Column: TColumnIndex) of object; + TVTAfterAutoFitColumnsEvent = procedure(Sender: TVTHeader) of object; + TVTColumnCheckChangingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var NewState: TCheckState; + var Allowed: Boolean) of object; + TVTColumnClickEvent = procedure (Sender: TBaseVirtualTree; Column: TColumnIndex; Shift: TShiftState) of object; + TVTColumnDblClickEvent = procedure (Sender: TBaseVirtualTree; Column: TColumnIndex; Shift: TShiftState) of object; + TColumnChangeEvent = procedure(const Sender: TBaseVirtualTree; const Column: TColumnIndex; Visible: Boolean) of object; + TVTColumnWidthDblClickResizeEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; Shift: TShiftState; P: TPoint; + var Allowed: Boolean) of object; + TVTBeforeColumnWidthTrackingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; Shift: TShiftState) of object; + TVTAfterColumnWidthTrackingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex) of object; + TVTColumnWidthTrackingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; Shift: TShiftState; var TrackPoint: TPoint; P: TPoint; + var Allowed: Boolean) of object; + TVTGetHeaderCursorEvent = procedure(Sender: TVTHeader; var Cursor: TVTCursor) of object; + TVTBeforeGetMaxColumnWidthEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var UseSmartColumnWidth: Boolean) of object; + TVTAfterGetMaxColumnWidthEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var MaxWidth: TDimension) of object; + TVTCanSplitterResizeColumnEvent = procedure(Sender: TVTHeader; P: TPoint; Column: TColumnIndex; var Allowed: Boolean) of object; + TVTCanSplitterResizeHeaderEvent = procedure(Sender: TVTHeader; P: TPoint; var Allowed: Boolean) of object; + + // move, copy and node tracking events + TVTNodeMovedEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; + TVTNodeMovingEvent = procedure(Sender: TBaseVirtualTree; Node, Target: PVirtualNode; + var Allowed: Boolean) of object; + TVTNodeCopiedEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; + TVTNodeCopyingEvent = procedure(Sender: TBaseVirtualTree; Node, Target: PVirtualNode; + var Allowed: Boolean) of object; + TVTNodeClickEvent = procedure(Sender: TBaseVirtualTree; const HitInfo: THitInfo) of object; + TVTNodeHeightTrackingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState; + var TrackPoint: TPoint; P: TPoint; var Allowed: Boolean) of object; + TVTNodeHeightDblClickResizeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; + Shift: TShiftState; P: TPoint; var Allowed: Boolean) of object; + TVTCanSplitterResizeNodeEvent = procedure(Sender: TBaseVirtualTree; P: TPoint; Node: PVirtualNode; + Column: TColumnIndex; var Allowed: Boolean) of object; + + TVTGetUserClipboardFormatsEvent = procedure(Sender: TBaseVirtualTree; var Formats: TFormatEtcArray) of object; + + // drag'n drop/OLE events + TVTCreateDragManagerEvent = procedure(Sender: TBaseVirtualTree; out DragManager: IVTDragManager) of object; + TVTCreateDataObjectEvent = procedure(Sender: TBaseVirtualTree; out IDataObject: TVTDragDataObject) of object; + TVTDragAllowedEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; + var Allowed: Boolean) of object; + TVTDragOverEvent = procedure(Sender: TBaseVirtualTree; Source: TObject; Shift: TShiftState; State: TDragState; + Pt: TPoint; Mode: TDropMode; var Effect: Integer; var Accept: Boolean) of object; + TVTDragDropEvent = procedure(Sender: TBaseVirtualTree; Source: TObject; DataObject: TVTDragDataObject; + Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode) of object; + + // paint events + TVTBeforeItemEraseEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; ItemRect: TRect; + var ItemColor: TColor; var EraseAction: TItemEraseAction) of object; + TVTAfterItemEraseEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; + ItemRect: TRect) of object; + TVTBeforeItemPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; + ItemRect: TRect; var CustomDraw: Boolean) of object; + TVTAfterItemPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; + ItemRect: TRect) of object; + TVTBeforeCellPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; + Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect) of object; + TVTAfterCellPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; + Column: TColumnIndex; CellRect: TRect) of object; + TVTPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas) of object; + TVTBackgroundPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; R: TRect; + var Handled: Boolean) of object; + TVTGetLineStyleEvent = procedure(Sender: TBaseVirtualTree; var Bits: Pointer) of object; + TVTMeasureItemEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; + var NodeHeight: TDimension) of object; + TVTPaintText = procedure(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; + TextType: TVSTTextType) of object; + + TVTPrepareButtonImagesEvent = procedure(Sender: TBaseVirtualTree; const APlusBM : TBitmap; const APlusHotBM :TBitmap; + const APlusSelectedHotBM :TBitmap; const AMinusBM : TBitmap; const AMinusHotBM : TBitmap; + const AMinusSelectedHotBM :TBitmap; var ASize : TSize) of object; + + TVTColumnHeaderSpanningEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var Count: Integer) of object; + + // search, sort + TVTCompareEvent = procedure(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex; + var Result: Integer) of object; + TVTIncrementalSearchEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; const SearchText: string; + var Result: Integer) of object; + + // operations + TVTOperationEvent = procedure(Sender: TBaseVirtualTree; OperationKind: TVTOperationKind) of object; + + TVTHintKind = (vhkText, vhkOwnerDraw); + TVTHintKindEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var Kind: TVTHintKind) of object; + TVTDrawHintEvent = procedure(Sender: TBaseVirtualTree; HintCanvas: TCanvas; Node: PVirtualNode; R: TRect; Column: TColumnIndex) of object; + TVTGetHintSizeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var R: TRect) of object; + + // miscellaneous + TVTBeforeDrawLineImageEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Level: Integer; var PosX: TDimension) of object; + TVTGetNodeDataSizeEvent = procedure(Sender: TBaseVirtualTree; var NodeDataSize: Integer) of object; + TVTKeyActionEvent = procedure(Sender: TBaseVirtualTree; var CharCode: Word; var Shift: TShiftState; + var DoDefault: Boolean) of object; + TVTScrollEvent = procedure(Sender: TBaseVirtualTree; DeltaX, DeltaY: TDimension) of object; + TVTUpdatingEvent = procedure(Sender: TBaseVirtualTree; State: TVTUpdateState) of object; + TVTGetCursorEvent = procedure(Sender: TBaseVirtualTree; var Cursor: TCursor) of object; + TVTStateChangeEvent = procedure(Sender: TBaseVirtualTree; Enter, Leave: TVirtualTreeStates) of object; + TVTGetCellIsEmptyEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; + var IsEmpty: Boolean) of object; + TVTScrollBarShowEvent = procedure(Sender: TBaseVirtualTree; Bar: Integer; Show: Boolean) of object; + + // Helper types for node iterations. + TGetFirstNodeProc = function: PVirtualNode of object; + TGetNextNodeProc = function(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode of object; + + TVZVirtualNodeEnumerationMode = ( + vneAll, + vneChecked, + vneChild, + vneCutCopy, + vneInitialized, + vneLeaf, + vneLevel, + vneNoInit, + vneSelected, + vneVisible, + vneVisibleChild, + vneVisibleNoInitChild, + vneVisibleNoInit + ); + + PVTVirtualNodeEnumeration = ^TVTVirtualNodeEnumeration; + + TVTVirtualNodeEnumerator = record + private + FNode: PVirtualNode; + FCanMoveNext: Boolean; + FEnumeration: PVTVirtualNodeEnumeration; + function GetCurrent: PVirtualNode; inline; + public + function MoveNext: Boolean; inline; + property Current: PVirtualNode read GetCurrent; + end; + + TVTVirtualNodeEnumeration = record + private + FMode: TVZVirtualNodeEnumerationMode; + FTree: TBaseVirtualTree; + // GetNextXxx parameters: + FConsiderChildrenAbove: Boolean; + FNode: PVirtualNode; + FNodeLevel: Cardinal; + FState: TCheckState; + FIncludeFiltered: Boolean; + public + function GetEnumerator: TVTVirtualNodeEnumerator; + private + function GetNext(Node: PVirtualNode): PVirtualNode; + end; + + + // ----- TBaseVirtualTree + TBaseVirtualTree = class abstract(TVTBaseAncestor) + private + FTotalInternalDataSize: Cardinal; // Cache of the sum of the necessary internal data size for all tree + FBorderStyle: TBorderStyle; + FHeader: TVTHeader; + FRoot: PVirtualNode; + FDefaultNodeHeight, + FIndent: TDimension; + FOptions: TCustomVirtualTreeOptions; + FUpdateCount: Cardinal; // update stopper, updates of the tree control are only done if = 0 + FSynchUpdateCount: Cardinal; // synchronizer, causes all events which are usually done via timers + // to happen immediately, regardless of the normal update state + FNodeDataSize: Integer; // number of bytes to allocate with each node (in addition to its base + // structure and the internal data), if -1 then do callback + FStates: TVirtualTreeStates; // various active/pending states the tree needs to consider + FLastSelected, + FFocusedNode: PVirtualNode; + FEditColumn, // column to be edited (focused node) + FFocusedColumn: TColumnIndex; // NoColumn if no columns are active otherwise the last hit column of + // the currently focused node + FHeightTrackPoint: TPoint; // Starting point of a node's height changing operation. + FHeightTrackNode: PVirtualNode; // Node which height is being changed. + FHeightTrackColumn: TColumnIndex; // Initial column where the height changing operation takes place. + FScrollDirections: TScrollDirections; // directions to scroll client area into depending on mouse position + FLastStructureChangeReason: TChangeReason; // Used for delayed structure change event. + FLastStructureChangeNode, // dito + FLastChangedNode, // used for delayed change event + FCurrentHotNode: PVirtualNode; // Node over which the mouse is hovering. + FCurrentHotColumn: TColumnIndex; // Column over which the mouse is hovering. + FHotNodeButtonHit: Boolean; // Indicates wether the mouse is hovering over the hot node's button. + FLastSelRect, + FNewSelRect: TRect; // used while doing draw selection + FHotCursor: TCursor; // can be set to additionally indicate the current hot node + FLastHitInfo: THitInfo; // The THitInfo of the last mouse-down event. + // in Win98 (slide) and Windows 2000 (fade)) + FHintMode: TVTHintMode; // determines the kind of the hint window + FHintData: TVTHintData; // used while preparing the hint window + FChangeDelay: Cardinal; // used to delay OnChange event + FEditDelay: Cardinal; // determines time to elapse before a node goes into edit mode + FPositionCache: TCache; // array which stores node references ordered by vertical positions + // (see also DoValidateCache for more information) + FVisibleCount: Cardinal; // number of currently visible nodes + FStartIndex: Cardinal; // index to start validating cache from + FSelection: TNodeArray; // list of currently selected nodes + FSelectionLocked: Boolean; // prevents the tree from changing the selection + FRangeAnchor: PVirtualNode; // anchor node for selection with the keyboard, determines start of a + // selection range + FCheckPropagationCount: Cardinal; // nesting level of check propagation (WL, 05.02.2004) + FLastSelectionLevel: Integer; // keeps the last node level for constrained multiselection + FDrawSelShiftState: TShiftState; // keeps the initial shift state when the user starts selection with + // the mouse + FEditLink: IVTEditLink; // used to comunicate with an application defined editor + FTempNodeCache: TNodeArray; // used at various places to hold temporarily a bunch of node refs. + FTempNodeCount: Cardinal; // number of nodes in FTempNodeCache + FBackground: TVTBackground; // A background image loadable at design and runtime. + FBackgroundImageTransparent: Boolean; // By default, this is off. When switched on, will try to draw the image + // transparent by using the color of the component as transparent color + + FMargin: TDimension; // horizontal distance to border and columns + FTextMargin: TDimension; // space between the node's text and its horizontal bounds + FBackgroundOffsetX, + FBackgroundOffsetY: TDimension; // used to fine tune the position of the background image + FAnimationDuration: Cardinal; // specifies how long an animation shall take (expanding, hint) + FWantTabs: Boolean; // If True then the tree also consumes the tab key. + FNodeAlignment: TVTNodeAlignment; // determines how to interpret the align member of a node + FHeaderRect: TRect; // Space which the header currently uses in the control (window coords). + FLastHintRect: TRect; // Area which the mouse must leave to reshow a hint. + FUpdateRect: TRect; + FEmptyListMessage: string; // Optional message that will be displayed if no nodes exist in the control. + + // paint support and images + FPlusBM, + FMinusBM, // small bitmaps used for tree buttons + FHotPlusBM, + FHotMinusBM, + FSelectedHotPlusBM, + FSelectedHotMinusBM: TBitmap; // small bitmaps used for hot tree buttons + FImages, // normal images in the tree + FStateImages, // state images in the tree + FCustomCheckImages: TCustomImageList; // application defined check images + FCheckImageKind: TCheckImageKind; // light or dark, cross marks or tick marks + FCheckImages: TCustomImageList; // Reference to global image list to be used for the check images. + //TODO: Use this margin for other images as well + FImagesMargin: TDimension; // The margin used left and right of the checkboxes. + FImageChangeLink, + FStateChangeLink, + FCustomCheckChangeLink: TChangeLink; // connections to the image lists + FOldFontChange: TNotifyEvent; // helper method pointer for tracking font changes in the off screen buffer + FColors: TVTColors; // class comprising all customizable colors in the tree + FButtonStyle: TVTButtonStyle; // style of the tree buttons + FButtonFillMode: TVTButtonFillMode; // for rectangular tree buttons only: how to fill them + FLineStyle: TVTLineStyle; // style of the tree lines + FLineMode: TVTLineMode; // tree lines or bands etc. + FSelectionCurveRadius: Cardinal; // radius for rounded selection rectangles + FSelectionBlendFactor: Byte; // Determines the factor by which the selection rectangle is to be + // faded if enabled. + FDrawSelectionMode: TVTDrawSelectionMode; // determines the paint mode for draw selection + + // alignment and directionality support + FAlignment: TAlignment; // default alignment of the tree if no columns are shown + + // drag'n drop and clipboard support + FDragImageKind: TVTDragImageKind; // determines whether or not and what to show in the drag image + FDragOperations: TDragOperations; // determines which operations are allowed during drag'n drop + FDragThreshold: Integer; // used to determine when to actually start a drag'n drop operation + FDragManager: IVTDragManager; // drag'n drop, cut'n paste + FDropTargetNode: PVirtualNode; // node currently selected as drop target + FLastDropMode: TDropMode; // set while dragging and used to track changes + FDragSelection: TNodeArray; // temporary copy of FSelection used during drag'n drop + FLastDragEffect: Integer; // The last executed drag effect + FDragType: TVTDragType; // used to switch between OLE and VCL drag'n drop + FDragWidth, + FDragHeight: Integer; // size of the drag image, the larger the more CPU power is needed + FClipboardFormats: TClipboardFormats; // a list of clipboard format descriptions enabled for this tree + FLastVCLDragTarget: PVirtualNode; // A node cache for VCL drag'n drop (keywords: DragLeave on DragDrop). + FVCLDragEffect: Integer; // A cache for VCL drag'n drop to keep the current drop effect. + + // scroll support + FScrollBarOptions: TScrollBarOptions; // common properties of horizontal and vertical scrollbar + FAutoScrollInterval: TAutoScrollInterval; // determines speed of auto scrolling + FAutoScrollDelay: Cardinal; // amount of milliseconds to wait until autoscrolling becomes active + FAutoExpandDelay: Cardinal; // amount of milliseconds to wait until a node is expanded if it is the + // drop target + FOffsetX: TDimension; + FOffsetY: TDimension; // Determines left and top scroll offset. + FEffectiveOffsetX: TDimension; // Actual position of the horizontal scroll bar (varies depending on bidi mode). + FRangeX, + FRangeY: TNodeHeight; // current virtual width and height of the tree + FBottomSpace: TDimension; // Extra space below the last node. + + FDefaultPasteMode: TVTNodeAttachMode; // Used to determine where to add pasted nodes to. + FDragScrollStart: Cardinal; // Contains the start time when a tree does auto scrolling as drop target. + + // search + FIncrementalSearch: TVTIncrementalSearch; // Used to determine whether and how incremental search is to be used. + FSearchTimeout: Cardinal; // Number of milliseconds after which to stop incremental searching. + FSearchBuffer: string; // Collects a sequence of keypresses used to do incremental searching. + FLastSearchNode: PVirtualNode; // Reference to node which was last found as search fit. + FSearchDirection: TVTSearchDirection; // Direction to incrementally search the tree. + FSearchStart: TVTSearchStart; // Where to start iteration on each key press. + + // miscellanous + FPanningWindow: TForm; // Helper window for wheel panning + FPanningCursor: TVTCursor; // Current wheel panning cursor. + FLastClickPos: TPoint; // Used for retained drag start and wheel mouse scrolling. + FOperationCount: Cardinal; // Counts how many nested long-running operations are in progress. + FOperationCanceled: Boolean; // Used to indicate that a long-running operation should be canceled. + FChangingTheme: Boolean; // Used to indicate that a theme change is goi ng on + FNextNodeToSelect: PVirtualNode; // Next tree node that we would like to select if the current one gets deleted or looses selection for other reasons. + FPendingSyncProcs:Integer; // Counter that indicates whether we have queued anonymous calls to the min thread, see issue #1199 + + // export + FOnBeforeNodeExport: TVTNodeExportEvent; // called before exporting a node + FOnNodeExport: TVTNodeExportEvent; + FOnAfterNodeExport: TVTNodeExportEvent; // called after exporting a node + FOnBeforeColumnExport: TVTColumnExportEvent; // called before exporting a column + FOnColumnExport: TVTColumnExportEvent; + FOnAfterColumnExport: TVTColumnExportEvent; // called after exporting a column + FOnBeforeTreeExport: TVTTreeExportEvent; // called before starting the export + FOnAfterTreeExport: TVTTreeExportEvent; // called after finishing the export + FOnBeforeHeaderExport: TVTTreeExportEvent; // called before exporting the header + FOnAfterHeaderExport: TVTTreeExportEvent; // called after exporting the header + + // common events + FOnChange: TVTChangeEvent; // selection change + FOnStructureChange: TVTStructureChangeEvent; // structural change like adding nodes etc. + FOnInitChildren: TVTInitChildrenEvent; // called when a node's children are needed (expanding etc.) + FOnInitNode: TVTInitNodeEvent; // called when a node needs to be initialized (child count etc.) + FOnFreeNode: TVTFreeNodeEvent; // called when a node is about to be destroyed, user data can and should + // be freed in this event + FOnGetImage: TVTGetImageEvent; // Used to retrieve the image index of a given node. + FOnGetImageEx: TVTGetImageExEvent; // Used to retrieve the image index of a given node along with a custom + // image list. + FOnGetImageText: TVTGetImageTextEvent; // Used to retrieve the image alternative text of a given node. + // Used by the accessibility interface to provide useful text for status images. + FOnHotChange: TVTHotNodeChangeEvent; // called when the current "hot" node (that is, the node under the mouse) + // changes and hot tracking is enabled + FOnExpanding, // called just before a node is expanded + FOnCollapsing: TVTChangingEvent; // called just before a node is collapsed + FOnChecking: TVTCheckChangingEvent; // called just before a node's check state is changed + FOnExpanded, // called after a node has been expanded + FOnCollapsed, // called after a node has been collapsed + FOnChecked: TVTChangeEvent; // called after a node's check state has been changed + FOnResetNode: TVTChangeEvent; // called when a node is set to be uninitialized + FOnNodeMoving: TVTNodeMovingEvent; // called just before a node is moved from one parent node to another + // (this can be cancelled) + FOnNodeMoved: TVTNodeMovedEvent; // called after a node and its children have been moved to another + // parent node (probably another tree, but within the same application) + FOnNodeCopying: TVTNodeCopyingEvent; // called when a node is copied to another parent node (probably in + // another tree, but within the same application, can be cancelled) + FOnNodeClick: TVTNodeClickEvent; // called when the user clicks on a node + FOnNodeDblClick: TVTNodeClickEvent; // called when the user double clicks on a node + FOnCanSplitterResizeNode: TVTCanSplitterResizeNodeEvent; // called to query the application wether resizing a node is allowed + FOnNodeHeightTracking: TVTNodeHeightTrackingEvent; // called when a node's height is being changed via mouse + FOnNodeHeightDblClickResize: TVTNodeHeightDblClickResizeEvent; // called when a node's vertical splitter is double clicked + FOnNodeCopied: TVTNodeCopiedEvent; // call after a node has been copied + FOnEditing: TVTEditChangingEvent; // called just before a node goes into edit mode + FOnEditCancelled: TVTEditCancelEvent; // called when editing has been cancelled + FOnEdited: TVTEditChangeEvent; // called when editing has successfully been finished + FOnFocusChanging: TVTFocusChangingEvent; // called when the focus is about to go to a new node and/or column + // (can be cancelled) + FOnFocusChanged: TVTFocusChangeEvent; // called when the focus goes to a new node and/or column + FOnAddToSelection: TVTAddToSelectionEvent; // called when a node is added to the selection + FOnRemoveFromSelection: TVTRemoveFromSelectionEvent; // called when a node is removed from the selection + FOnGetPopupMenu: TVTPopupEvent; // called when the popup for a node or the header needs to be shown + FOnGetHelpContext: TVTHelpContextEvent; // called when a node specific help theme should be called + FOnCreateEditor: TVTCreateEditorEvent; // called when a node goes into edit mode, this allows applications + // to supply their own editor + FOnLoadNode, // called after a node has been loaded from a stream (file, clipboard, + // OLE drag'n drop) to allow an application to load their own data + // saved in OnSaveNode + FOnSaveNode: TVTSaveNodeEvent; // called when a node needs to be serialized into a stream + // (see OnLoadNode) to give the application the opportunity to save + // their node specific, persistent data (note: never save memory + // references) + FOnLoadTree, // called after the tree has been loaded from a stream to allow an + // application to load their own data saved in OnSaveTree + FOnSaveTree: TVTSaveTreeEvent; // called after the tree has been saved to a stream to allow an + // application to save its own data + + // header/column mouse events + FOnAfterAutoFitColumn: TVTAfterAutoFitColumnEvent; + FOnAfterAutoFitColumns: TVTAfterAutoFitColumnsEvent; + FOnBeforeAutoFitColumns: TVTBeforeAutoFitColumnsEvent; + FOnBeforeAutoFitColumn: TVTBeforeAutoFitColumnEvent; + FOnHeaderAddPopupItem: TVTHeaderAddPopupItemEvent; + FOnHeaderClick: TVTHeaderClickEvent; + FOnHeaderDblClick: TVTHeaderClickEvent; + FOnAfterHeaderHeightTracking: TVTAfterHeaderHeightTrackingEvent; + FOnBeforeHeaderHeightTracking: TVTBeforeHeaderHeightTrackingEvent; + FOnHeaderHeightTracking: TVTHeaderHeightTrackingEvent; + FOnHeaderHeightDblClickResize: TVTHeaderHeightDblClickResizeEvent; + FOnHeaderMouseDown, + FOnHeaderMouseUp: TVTHeaderMouseEvent; + FOnHeaderMouseMove: TVTHeaderMouseMoveEvent; + FOnAfterGetMaxColumnWidth: TVTAfterGetMaxColumnWidthEvent; + FOnBeforeGetMaxColumnWidth: TVTBeforeGetMaxColumnWidthEvent; + FOnColumnChecked: TVTHeaderNotifyEvent; // triggered when the column is about to be checked + FOnColumnChecking: TVTColumnCheckChangingEvent; + FOnColumnClick: TVTColumnClickEvent; + FOnColumnDblClick: TVTColumnDblClickEvent; + FOnColumnResize: TVTHeaderNotifyEvent; + fOnColumnVisibilityChanged: TColumnChangeEvent; + FOnColumnWidthDblClickResize: TVTColumnWidthDblClickResizeEvent; + FOnAfterColumnWidthTracking: TVTAfterColumnWidthTrackingEvent; + FOnBeforeColumnWidthTracking: TVTBeforeColumnWidthTrackingEvent; + FOnColumnWidthTracking: TVTColumnWidthTrackingEvent; + FOnGetHeaderCursor: TVTGetHeaderCursorEvent; // triggered to allow the app. to use customized cursors for the header + FOnCanSplitterResizeColumn: TVTCanSplitterResizeColumnEvent; + FOnCanSplitterResizeHeader: TVTCanSplitterResizeHeaderEvent; + + // paint events + FOnAfterPaint, // triggered when the tree has entirely been painted + FOnBeforePaint: TVTPaintEvent; // triggered when the tree is about to be painted + FOnAfterItemPaint: TVTAfterItemPaintEvent; // triggered after an item has been painted + FOnBeforeItemPaint: TVTBeforeItemPaintEvent; // triggered when an item is about to be painted + FOnBeforeItemErase: TVTBeforeItemEraseEvent; // triggered when an item's background is about to be erased + FOnAfterItemErase: TVTAfterItemEraseEvent; // triggered after an item's background has been erased + FOnAfterCellPaint: TVTAfterCellPaintEvent; // triggered after a column of an item has been painted + FOnBeforeCellPaint: TVTBeforeCellPaintEvent; // triggered when a column of an item is about to be painted + FOnHeaderDraw: TVTHeaderPaintEvent; // Used when owner draw is enabled for the header and a column is set + // to owner draw mode. + FOnPrepareButtonImages : TVTPrepareButtonImagesEvent; //allow use to customise plus/minus bitmap images + FOnHeaderDrawQueryElements: TVTHeaderPaintQueryElementsEvent; // Used for advanced header painting to query the + // application for the elements, which are drawn by it and which should + // be drawn by the tree. + FOnAdvancedHeaderDraw: TVTAdvancedHeaderPaintEvent; // Used when owner draw is enabled for the header and a column + // is set to owner draw mode. But only if OnHeaderDrawQueryElements + // returns at least one element to be drawn by the application. + // In this case OnHeaderDraw is not used. + FOnGetLineStyle: TVTGetLineStyleEvent; // triggered when a custom line style is used and the pattern brush + // needs to be build + FOnPaintBackground: TVTBackgroundPaintEvent; // triggered if a part of the tree's background must be erased which is + // not covered by any node + FOnMeasureItem: TVTMeasureItemEvent; // Triggered when a node is about to be drawn and its height was not yet + // determined by the application. + FOnColumnHeaderSpanning: TVTColumnHeaderSpanningEvent; // triggered before the header column area been create for painting + FOnGetUserClipboardFormats: TVTGetUserClipboardFormatsEvent; // gives application/descendants the opportunity to + // add own clipboard formats on the fly + FOnPaintText: TVTPaintText; // triggered before either normal or fixed text is painted to allow + // even finer customization (kind of sub cell painting) + // drag'n drop events + FOnCreateDragManager: TVTCreateDragManagerEvent; // called to allow for app./descendant defined drag managers + FOnCreateDataObject: TVTCreateDataObjectEvent; // called to allow for app./descendant defined data objects + FOnDragAllowed: TVTDragAllowedEvent; // used to get permission for manual drag in mouse down + FOnDragOver: TVTDragOverEvent; // called for every mouse move + FOnDragDrop: TVTDragDropEvent; // called on release of mouse button (if drop was allowed) + FOnHeaderDragged: TVTHeaderDraggedEvent; // header (column) drag'n drop + FOnHeaderDraggedOut: TVTHeaderDraggedOutEvent; // header (column) drag'n drop, which did not result in a valid drop. + FOnHeaderDragging: TVTHeaderDraggingEvent; // header (column) drag'n drop + + // miscellanous events + FOnGetNodeDataSize: TVTGetNodeDataSizeEvent; // Called if NodeDataSize is -1. + FOnBeforeDrawLineImage: TVTBeforeDrawLineImageEvent; // Called to allow adjusting the indention of treelines. + FOnKeyAction: TVTKeyActionEvent; // Used to selectively prevent key actions (full expand on Ctrl+'+' etc.). + FOnScroll: TVTScrollEvent; // Called when one or both paint offsets changed. + FOnUpdating: TVTUpdatingEvent; // Called from BeginUpdate, EndUpdate, BeginSynch and EndSynch. + FOnGetCursor: TVTGetCursorEvent; // Called to allow the app. to set individual cursors. + FOnStateChange: TVTStateChangeEvent; // Called whenever a state in the tree changes. + FOnGetCellIsEmpty: TVTGetCellIsEmptyEvent; // Called when the tree needs to know if a cell is empty. + FOnShowScrollBar: TVTScrollBarShowEvent; // Called when a scrollbar is changed in its visibility. + FOnBeforeGetCheckState: TVTBeforeGetCheckStateEvent; // Called before a CheckState for a Node is obtained. + // Gives the application a chance to do special processing + // when a check state is actually required for the first time. + + // search, sort + FOnCompareNodes: TVTCompareEvent; // used during sort + FOnDrawHint: TVTDrawHintEvent; + FOnGetHintSize: TVTGetHintSizeEvent; + FOnGetHintKind: TVTHintKindEvent; + FOnIncrementalSearch: TVTIncrementalSearchEvent; // triggered on every key press (not key down) + FOnMouseEnter: TNotifyEvent; + FOnMouseLeave: TNotifyEvent; + + // operations + FOnStartOperation: TVTOperationEvent; // Called when an operation starts + FOnEndOperation: TVTOperationEvent; // Called when an operation ends + + FVclStyleEnabled: Boolean; + FSelectionCount: Integer; + + procedure CMStyleChanged(var Message: TMessage); message CM_STYLECHANGED; + procedure CMParentDoubleBufferedChange(var Message: TMessage); message CM_PARENTDOUBLEBUFFEREDCHANGED; + + procedure AdjustTotalCount(Node: PVirtualNode; Value: Integer; relative: Boolean = False); + function CalculateCacheEntryCount: Integer; + procedure CalculateVerticalAlignments(var PaintInfo: TVTPaintInfo; var VButtonAlign: TDimension); + function ChangeCheckState(Node: PVirtualNode; Value: TCheckState): Boolean; + function CollectSelectedNodesLTR(MainColumn: Integer; NodeLeft, NodeRight: TDimension; Alignment: TAlignment; OldRect, + NewRect: TRect): Boolean; + function CollectSelectedNodesRTL(MainColumn: Integer; NodeLeft, NodeRight: TDimension; Alignment: TAlignment; OldRect, + NewRect: TRect): Boolean; + procedure ClearNodeBackground(const PaintInfo: TVTPaintInfo; UseBackground, Floating: Boolean; R: TRect); + function CompareNodePositions(Node1, Node2: PVirtualNode; ConsiderChildrenAbove: Boolean = False): Integer; + procedure DrawLineImage(const PaintInfo: TVTPaintInfo; X, Y, H, VAlign: TDimension; Style: TVTLineType; Reverse: Boolean); + function FindInPositionCache(Node: PVirtualNode; var CurrentPos: TNodeHeight): PVirtualNode; overload; + function FindInPositionCache(Position: TDimension; var CurrentPos: TNodeHeight): PVirtualNode; overload; + procedure FixupTotalCount(Node: PVirtualNode); + procedure FixupTotalHeight(Node: PVirtualNode); + function GetBottomNode: PVirtualNode; + function GetCheckState(Node: PVirtualNode): TCheckState; + function GetCheckType(Node: PVirtualNode): TCheckType; + function GetChildCount(Node: PVirtualNode): Cardinal; + function GetChildrenInitialized(Node: PVirtualNode): Boolean; inline; + function GetCutCopyCount: Integer; + function GetDisabled(Node: PVirtualNode): Boolean; + function GetSyncCheckstateWithSelection(Node: PVirtualNode): Boolean; + function GetDragManager: IVTDragManager; + function GetExpanded(Node: PVirtualNode): Boolean; + function GetFiltered(Node: PVirtualNode): Boolean; + function GetFullyVisible(Node: PVirtualNode): Boolean; + function GetHasChildren(Node: PVirtualNode): Boolean; + function GetMultiline(Node: PVirtualNode): Boolean; + function GetNodeHeight(Node: PVirtualNode): TNodeHeight; + function GetNodeParent(Node: PVirtualNode): PVirtualNode; + function GetOffsetXY: TPoint; + function GetRootNodeCount: Cardinal; + function GetSelected(Node: PVirtualNode): Boolean; + function GetTopNode: PVirtualNode; + function GetTotalCount: Cardinal; + function GetVerticalAlignment(Node: PVirtualNode): Byte; + function GetVisible(Node: PVirtualNode): Boolean; + function GetVisiblePath(Node: PVirtualNode): Boolean; + function HandleDrawSelection(X, Y: TDimension): Boolean; + procedure HandleCheckboxClick(pHitNode: PVirtualNode; pKeys: LongInt); + function HasVisibleNextSibling(Node: PVirtualNode): Boolean; + function HasVisiblePreviousSibling(Node: PVirtualNode): Boolean; + procedure ImageListChange(Sender: TObject); + procedure InitializeFirstColumnValues(var PaintInfo: TVTPaintInfo); + procedure InitRootNode(OldSize: Cardinal = 0); + function IsFirstVisibleChild(Parent, Node: PVirtualNode): Boolean; + function IsLastVisibleChild(Parent, Node: PVirtualNode): Boolean; + function MakeNewNode: PVirtualNode; + function PackArray({*}const TheArray: TNodeArray; Count: Integer): Integer; + procedure FakeReadIdent(Reader: TReader); + procedure SetAlignment(const Value: TAlignment); + procedure SetAnimationDuration(const Value: Cardinal); + procedure SetBackground(const Value: TVTBackground); + procedure SetBackGroundImageTransparent(const Value: Boolean); + procedure SetBackgroundOffset(const Index: Integer; const Value: TDimension); + procedure SetBorderStyle(Value: TBorderStyle); + procedure SetBottomNode(Node: PVirtualNode); + procedure SetBottomSpace(const Value: TDimension); + procedure SetButtonFillMode(const Value: TVTButtonFillMode); + procedure SetButtonStyle(const Value: TVTButtonStyle); + procedure SetCheckImageKind(Value: TCheckImageKind); + procedure SetCheckState(Node: PVirtualNode; Value: TCheckState); + procedure SetCheckType(Node: PVirtualNode; Value: TCheckType); + procedure SetClipboardFormats(const Value: TClipboardFormats); + procedure SetColors(const Value: TVTColors); + procedure SetCustomCheckImages(const Value: TCustomImageList); + procedure SetDefaultNodeHeight(Value: TDimension); + procedure SetDisabled(Node: PVirtualNode; Value: Boolean); + procedure SetEmptyListMessage(const Value: string); + procedure SetExpanded(Node: PVirtualNode; Value: Boolean); + procedure SetFocusedColumn(Value: TColumnIndex); + procedure SetFocusedNode(Value: PVirtualNode); + procedure SetFullyVisible(Node: PVirtualNode; Value: Boolean); + procedure SetHasChildren(Node: PVirtualNode; Value: Boolean); + procedure SetHeader(const Value: TVTHeader); + procedure SetHotNode(Value: PVirtualNode); + procedure SetFiltered(Node: PVirtualNode; Value: Boolean); + procedure SetImages(const Value: TCustomImageList); + procedure SetIndent(Value: TDimension); + procedure SetLineMode(const Value: TVTLineMode); + procedure SetLineStyle(const Value: TVTLineStyle); + procedure SetMargin(Value: TDimension); + procedure SetMultiline(Node: PVirtualNode; const Value: Boolean); + procedure SetNodeAlignment(const Value: TVTNodeAlignment); + procedure SetNodeDataSize(Value: Integer); + procedure SetNodeHeight(Node: PVirtualNode; Value: TNodeHeight); + procedure SetNodeParent(Node: PVirtualNode; const Value: PVirtualNode); + procedure SetOffsetX(const Value: TDimension); + procedure SetOffsetXY(const Value: TPoint); + procedure SetOffsetY(const Value: TDimension); + procedure SetOptions(const Value: TCustomVirtualTreeOptions); + procedure SetRootNodeCount(Value: Cardinal); + procedure SetScrollBarOptions(Value: TScrollBarOptions); + procedure SetSearchOption(const Value: TVTIncrementalSearch); + procedure SetSelected(Node: PVirtualNode; Value: Boolean); + procedure SetSelectionCurveRadius(const Value: Cardinal); + procedure SetStateImages(const Value: TCustomImageList); + procedure SetTextMargin(Value: TDimension); + procedure SetTopNode(Node: PVirtualNode); + procedure SetUpdateState(Updating: Boolean); + procedure SetVerticalAlignment(Node: PVirtualNode; Value: Byte); + procedure SetVisible(Node: PVirtualNode; Value: Boolean); + procedure SetVisiblePath(Node: PVirtualNode; Value: Boolean); + procedure PrepareBackGroundPicture(Source: TVTBackground; DrawingBitmap: TBitmap; DrawingBitmapWidth: TDimension; DrawingBitmapHeight: TDimension; ABkgcolor: TColor); + procedure StaticBackground(Source: TVTBackground; Target: TCanvas; OffsetPosition: TPoint; R: TRect; aBkgColor: TColor); + procedure TileBackground(Source: TVTBackground; Target: TCanvas; Offset: TPoint; R: TRect; aBkgColor: TColor); + function ToggleCallback(Step, StepSize: Integer; Data: Pointer): Boolean; + + procedure CMColorChange(var Message: TMessage); message CM_COLORCHANGED; + procedure CMCtl3DChanged(var Message: TMessage); message CM_CTL3DCHANGED; + procedure CMBiDiModeChanged(var Message: TMessage); message CM_BIDIMODECHANGED; + procedure CMBorderChanged(var Message: TMessage); message CM_BORDERCHANGED; + procedure CMDenySubclassing(var Message: TMessage); message CM_DENYSUBCLASSING; + procedure CMDrag(var Message: TCMDrag); message CM_DRAG; + procedure CMEnabledChanged(var Message: TMessage); message CM_ENABLEDCHANGED; + procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED; + procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW; + procedure CMHintShowPause(var Message: TCMHintShowPause); message CM_HINTSHOWPAUSE; + procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER; + procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE; + procedure CMMouseWheel(var Message: TCMMouseWheel); message CM_MOUSEWHEEL; + procedure CMSysColorChange(var Message: TMessage); message CM_SYSCOLORCHANGE; + procedure TVMGetItem(var Message: TMessage); message TVM_GETITEM; + procedure TVMGetItemRect(var Message: TMessage); message TVM_GETITEMRECT; + procedure TVMGetNextItem(var Message: TMessage); message TVM_GETNEXTITEM; + procedure WMCancelMode(var Message: TWMCancelMode); message WM_CANCELMODE; + procedure WMChar(var Message: TWMChar); message WM_CHAR; + procedure WMContextMenu(var Message: TWMContextMenu); message WM_CONTEXTMENU; + procedure WMCopy(var Message: TWMCopy); message WM_COPY; + procedure WMCut(var Message: TWMCut); message WM_CUT; + procedure WMEnable(var Message: TWMEnable); message WM_ENABLE; + procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND; + procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE; + procedure WMHScroll(var Message: TWMHScroll); message WM_HSCROLL; + procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN; + procedure WMKeyUp(var Message: TWMKeyUp); message WM_KEYUP; + procedure WMKillFocus(var Msg: TWMKillFocus); message WM_KILLFOCUS; + procedure WMLButtonDblClk(var Message: TWMLButtonDblClk); message WM_LBUTTONDBLCLK; + procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN; + procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP; + procedure WMMButtonDblClk(var Message: TWMMButtonDblClk); message WM_MBUTTONDBLCLK; + procedure WMMButtonDown(var Message: TWMMButtonDown); message WM_MBUTTONDOWN; + procedure WMMButtonUp(var Message: TWMMButtonUp); message WM_MBUTTONUP; + procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE; + procedure WMNCDestroy(var Message: TWMNCDestroy); message WM_NCDESTROY; + procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST; + procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT; + procedure WMPaint(var Message: TWMPaint); message WM_PAINT; + procedure WMPaste(var Message: TWMPaste); message WM_PASTE; + procedure WMPrint(var Message: TWMPrint); message WM_PRINT; + procedure WMRButtonDblClk(var Message: TWMRButtonDblClk); message WM_RBUTTONDBLCLK; + procedure WMRButtonDown(var Message: TWMRButtonDown); message WM_RBUTTONDOWN; + procedure WMRButtonUp(var Message: TWMRButtonUp); message WM_RBUTTONUP; + procedure WMSetCursor(var Message: TWMSetCursor); message WM_SETCURSOR; + procedure WMSetFocus(var Msg: TWMSetFocus); message WM_SETFOCUS; + procedure WMSize(var Message: TWMSize); message WM_SIZE; + procedure WMTimer(var Message: TWMTimer); message WM_TIMER; + procedure WMThemeChanged(var Message: TMessage); message WM_THEMECHANGED; + procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL; + function GetRangeX: TDimension; + procedure SetDoubleBuffered(const Value: Boolean); + function GetVclStyleEnabled: Boolean; inline; + procedure SetOnPrepareButtonImages(const Value: TVTPrepareButtonImagesEvent); + function IsStored_BackgroundOffsetXY(const Index: Integer): Boolean; + function IsStored_BottomSpace: Boolean; + function IsStored_DefaultNodeHeight: Boolean; + function IsStored_Indent: Boolean; + function IsStored_Margin: Boolean; + function IsStored_TextMargin: Boolean; + protected + FFontChanged: Boolean; // flag for keeping informed about font changes in the off screen buffer // [IPK] - private to protected + procedure AutoScale(); virtual; + procedure AddToSelection(const NewItems: TNodeArray; NewLength: Integer; ForceInsert: Boolean = False); overload; virtual; + procedure AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; var NextNonEmpty: TColumnIndex); virtual; + procedure AdjustPanningCursor(X, Y: TDimension); virtual; + procedure AdjustTotalHeight(Node: PVirtualNode; Value: TNodeHeight; relative: Boolean = False); + procedure AdviseChangeEvent(StructureChange: Boolean; Node: PVirtualNode; Reason: TChangeReason); virtual; + function AllocateInternalDataArea(Size: Cardinal): Cardinal; virtual; + procedure Animate(Steps, Duration: Cardinal; Callback: TVTAnimationCallback; Data: Pointer); virtual; + function CalculateSelectionRect(X, Y: TDimension): Boolean; virtual; + function CanAutoScroll: Boolean; virtual; + function CanShowDragImage: Boolean; virtual; + function CanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex): Boolean; + procedure Change(Node: PVirtualNode); virtual; + procedure ChangeTreeStatesAsync(EnterStates, LeaveStates: TVirtualTreeStates); + procedure ChangeScale(M, D: Integer{$if CompilerVersion >= 31}; isDpiChange: Boolean{$ifend}); override; + function CheckParentCheckState(Node: PVirtualNode; NewCheckState: TCheckState): Boolean; virtual; + procedure ClearDragManager; + procedure ClearSelection(pFireChangeEvent: Boolean); overload; virtual; + procedure ClearTempCache; virtual; + function ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; + function ComputeRTLOffset(ExcludeScrollBar: Boolean = False): TDimension; virtual; + function CountLevelDifference(Node1, Node2: PVirtualNode): Integer; virtual; + function CountVisibleChildren(Node: PVirtualNode): Cardinal; virtual; + procedure CreateParams(var Params: TCreateParams); override; + procedure CreateWnd; override; + procedure DecVisibleCount; + procedure DefineProperties(Filer: TFiler); override; + procedure DeleteNode(Node: PVirtualNode; Reindex: Boolean; ParentClearing: Boolean); overload; + function DetermineDropMode(const P: TPoint; var HitInfo: THitInfo; var NodeRect: TRect): TDropMode; virtual; + procedure DetermineHiddenChildrenFlag(Node: PVirtualNode); virtual; + procedure DetermineHiddenChildrenFlagAllNodes; virtual; + procedure DetermineHitPositionLTR(var HitInfo: THitInfo; Offset, Right: TDimension; Alignment: TAlignment); virtual; + procedure DetermineHitPositionRTL(var HitInfo: THitInfo; Offset, Right: TDimension; Alignment: TAlignment); virtual; + function DetermineLineImageAndSelectLevel(Node: PVirtualNode; var LineImage: TLineImage): Integer; virtual; + function DetermineNextCheckState(CheckType: TCheckType; CheckState: TCheckState): TCheckState; virtual; + function DetermineScrollDirections(X, Y: TDimension): TScrollDirections; virtual; + procedure DoAddToSelection(Node: PVirtualNode); virtual; + procedure DoAdvancedHeaderDraw(var PaintInfo: THeaderPaintInfo; const Elements: THeaderPaintElements); virtual; + procedure DoAfterCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect); virtual; + procedure DoAfterItemErase(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect); virtual; + procedure DoAfterItemPaint(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect); virtual; + procedure DoAfterPaint(Canvas: TCanvas); virtual; + procedure DoAutoScroll(X, Y: TDimension); virtual; + function DoBeforeDrag(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; + procedure DoBeforeCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; + CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); virtual; + procedure DoBeforeItemErase(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect; var Color: TColor; + var EraseAction: TItemEraseAction); virtual; + function DoBeforeItemPaint(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect): Boolean; virtual; + procedure DoBeforePaint(Canvas: TCanvas); virtual; + function DoCancelEdit: Boolean; virtual; + procedure DoCanEdit(Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); virtual; + procedure DoCanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex; + var Allowed: Boolean); virtual; + procedure DoChange(Node: PVirtualNode); virtual; + procedure DoCheckClick(Node: PVirtualNode; NewCheckState: TCheckState); virtual; + procedure DoChecked(Node: PVirtualNode); virtual; + function DoChecking(Node: PVirtualNode; var NewCheckState: TCheckState): Boolean; virtual; + procedure DoCollapsed(Node: PVirtualNode); virtual; + function DoCollapsing(Node: PVirtualNode): Boolean; virtual; + procedure DoColumnChecked(Column: TColumnIndex); virtual; + function DoColumnChecking(Column: TColumnIndex; var NewCheckState: TCheckState): Boolean; virtual; + procedure DoColumnClick(Column: TColumnIndex; Shift: TShiftState); virtual; + procedure DoColumnDblClick(Column: TColumnIndex; Shift: TShiftState); virtual; + procedure DoColumnResize(Column: TColumnIndex); virtual; + procedure DoColumnVisibilityChanged(const Column: TColumnIndex; Visible: Boolean); virtual; + function DoCompare(Node1, Node2: PVirtualNode; Column: TColumnIndex): Integer; virtual; + function DoCreateDataObject: IDataObject; virtual; + function DoCreateDragManager: IVTDragManager; virtual; + function DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; virtual; + procedure DoDragging(P: TPoint); virtual; + procedure DoDragExpand; virtual; + procedure DoBeforeDrawLineImage(Node: PVirtualNode; Level: Integer; var XPos: TDimension); virtual; + function DoDragOver(Source: TObject; Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode; + var Effect: Integer): Boolean; virtual; + procedure DoDragDrop(Source: TObject; const DataObject: TVTDragDataObject; const Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; + var Effect: Integer; Mode: TDropMode); virtual; + procedure DoDrawHint(Canvas: TCanvas; Node: PVirtualNode; R: TRect; Column: + TColumnIndex); + procedure DoEdit; virtual; + procedure DoEndDrag(Target: TObject; X, Y: TDimension); override; + function DoEndEdit(pCancel: Boolean = False): Boolean; virtual; + procedure DoEndOperation(OperationKind: TVTOperationKind); virtual; + procedure DoEnter(); override; + procedure DoExpanded(Node: PVirtualNode); virtual; + function DoExpanding(Node: PVirtualNode): Boolean; virtual; + procedure DoFocusChange(Node: PVirtualNode; Column: TColumnIndex); virtual; + function DoFocusChanging(OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex): Boolean; virtual; + procedure DoFocusNode(Node: PVirtualNode; Ask: Boolean); virtual; + procedure DoFreeNode(Node: PVirtualNode); virtual; + function DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex; + CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; virtual; + procedure DoGetCursor(var Cursor: TCursor); virtual; + procedure DoGetHeaderCursor(var Cursor: TVTCursor); virtual; + procedure DoGetHintSize(Node: PVirtualNode; Column: TColumnIndex; var R: + TRect); virtual; + procedure DoGetHintKind(Node: PVirtualNode; Column: TColumnIndex; var Kind: + TVTHintKind); + function DoGetImageIndex(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; + var Ghosted: Boolean; var Index: TImageIndex): TCustomImageList; virtual; + procedure DoGetImageText(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var ImageText: string); virtual; + procedure DoGetLineStyle(var Bits: Pointer); virtual; + function DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): string; virtual; + function DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): string; virtual; + function DoGetNodeExtraWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): TDimension; virtual; + function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): TDimension; virtual; + function DoGetPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Position: TPoint): TPopupMenu; virtual; + procedure DoGetUserClipboardFormats(var Formats: TFormatEtcArray); virtual; + procedure DoHeaderAddPopupItem(const Column: TColumnIndex; var Cmd: TAddPopupItemType); + procedure DoHeaderClick(const HitInfo: TVTHeaderHitInfo); virtual; + procedure DoHeaderDblClick(const HitInfo: TVTHeaderHitInfo); virtual; + procedure DoHeaderDragged(Column: TColumnIndex; OldPosition: TColumnPosition); virtual; + procedure DoHeaderDraggedOut(Column: TColumnIndex; DropPosition: TPoint); virtual; + function DoHeaderDragging(Column: TColumnIndex): Boolean; virtual; + procedure DoHeaderDraw(Canvas: TCanvas; Column: TVirtualTreeColumn; R: TRect; Hover, Pressed: Boolean; + DropMark: TVTDropMarkMode); virtual; + procedure DoHeaderDrawQueryElements(var PaintInfo: THeaderPaintInfo; var Elements: THeaderPaintElements); virtual; + procedure DoHeaderMouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: TDimension); virtual; + procedure DoHeaderMouseMove(Shift: TShiftState; X, Y: TDimension); virtual; + procedure DoHeaderMouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: TDimension); virtual; + procedure DoHotChange(Old, New: PVirtualNode); virtual; + function DoIncrementalSearch(Node: PVirtualNode; const Text: string): Integer; virtual; + function DoInitChildren(Node: PVirtualNode; var ChildCount: Cardinal): Boolean; virtual; + procedure DoInitNode(Parent, Node: PVirtualNode; var InitStates: TVirtualNodeInitStates); virtual; + function DoKeyAction(var CharCode: Word; var Shift: TShiftState): Boolean; virtual; + procedure DoLoadUserData(Node: PVirtualNode; Stream: TStream); virtual; + procedure DoMeasureItem(TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: TDimension); virtual; + procedure DoMouseEnter(); override; + procedure DoMouseLeave(); override; + procedure DoNodeCopied(Node: PVirtualNode); virtual; + function DoNodeCopying(Node, NewParent: PVirtualNode): Boolean; virtual; + procedure DoNodeClick(const HitInfo: THitInfo); virtual; + procedure DoNodeDblClick(const HitInfo: THitInfo); virtual; + function DoNodeHeightDblClickResize(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState; + P: TPoint): Boolean; virtual; + function DoNodeHeightTracking(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState; + var TrackPoint: TPoint; P: TPoint): Boolean; virtual; + procedure DoNodeMoved(Node: PVirtualNode); virtual; + function DoNodeMoving(Node, NewParent: PVirtualNode): Boolean; virtual; + function DoPaintBackground(Canvas: TCanvas; R: TRect): Boolean; virtual; + procedure DoPaintDropMark(Canvas: TCanvas; Node: PVirtualNode; R: TRect); virtual; + procedure DoPaintNode(var PaintInfo: TVTPaintInfo); virtual; + procedure DoPaintText(Node: PVirtualNode; const Canvas: TCanvas; Column: TColumnIndex; TextType: TVSTTextType); virtual; + procedure DoPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Position: TPoint); virtual; + procedure DoRemoveFromSelection(Node: PVirtualNode); virtual; + procedure DoReset(Node: PVirtualNode); virtual; + procedure DoSaveUserData(Node: PVirtualNode; Stream: TStream); virtual; + procedure DoScroll(DeltaX, DeltaY: TDimension); virtual; + function DoSetOffsetXY(Value: TPoint; Options: TScrollUpdateOptions; ClipRect: PRect = nil): Boolean; virtual; + procedure DoShowScrollBar(Bar: Integer; Show: Boolean); virtual; + procedure DoStartDrag(var DragObject: TDragObject); override; + procedure DoStartOperation(OperationKind: TVTOperationKind); virtual; + procedure DoStateChange(Enter: TVirtualTreeStates; Leave: TVirtualTreeStates = []); override; + procedure DoStructureChange(Node: PVirtualNode; Reason: TChangeReason); virtual; + procedure DoTimerScroll; virtual; + procedure DoUpdating(State: TVTUpdateState); virtual; + procedure DoColumnHeaderSpanning(Column: TColumnIndex; var Count: Integer); virtual; + function DoValidateCache: Boolean; virtual; + procedure DragAndDrop(AllowedEffects: DWord; const DataObject: TVTDragDataObject; var DragEffect: Integer); virtual; + procedure DragCanceled; override; + function DragDrop(const DataObject: TVTDragDataObject; KeyState: Integer; Pt: TPoint; + var Effect: Integer): HResult; reintroduce; virtual; + function DragEnter(KeyState: Integer; Pt: TPoint; var Effect: Integer): HResult; virtual; + procedure DragFinished; virtual; + procedure DragLeave; virtual; + function DragOver(Source: TObject; KeyState: Integer; DragState: TDragState; Pt: TPoint; + var Effect: Integer): HResult; reintroduce; virtual; + procedure DrawDottedHLine(const PaintInfo: TVTPaintInfo; Left, Right, Top: TDimension); virtual; + procedure DrawDottedVLine(const PaintInfo: TVTPaintInfo; Top, Bottom, Left: TDimension); virtual; + procedure DrawGridHLine(const PaintInfo: TVTPaintInfo; Left, Right, Top: TDimension); virtual; + procedure DrawGridVLine(const PaintInfo: TVTPaintInfo; Top, Bottom, Left: TDimension; pFixedColumn: Boolean = False); virtual; + procedure EndOperation(OperationKind: TVTOperationKind); + procedure EnsureNodeFocused(); virtual; + function FindNodeInSelection(P: PVirtualNode; var Index: Integer; LowBound, HighBound: Integer): Boolean; virtual; + procedure FinishChunkHeader(Stream: TStream; StartPos, EndPos: Integer); virtual; + procedure FontChanged(AFont: TObject); virtual; + function GetBorderDimensions: TSize; virtual; + function GetCheckedCount: Integer; + function GetCheckImage(Node: PVirtualNode; ImgCheckType: TCheckType = ctNone; + ImgCheckState: TCheckState = csUncheckedNormal; ImgEnabled: Boolean = True): Integer; virtual; + function GetColumnClass: TVirtualTreeColumnClass; virtual; + function GetDefaultHintKind: TVTHintKind; virtual; + function GetDoubleBuffered: Boolean; {$if CompilerVersion >= 36}override;{$ifend} + function GetHeaderClass: TVTHeaderClass; virtual; + function GetHintWindowClass: THintWindowClass; virtual; abstract; + procedure GetImageIndex(var Info: TVTPaintInfo; Kind: TVTImageKind; InfoIndex: TVTImageInfoIndex); virtual; + function GetImageSize(Node: PVirtualNode; Kind: TVTImageKind = TVTImageKind.ikNormal; Column: TColumnIndex = 0; IncludePadding: Boolean = True): TSize; virtual; + function GetNodeImageSize(Node: PVirtualNode): TSize; virtual; deprecated 'Use GetImageSize instead'; + function GetMaxRightExtend: TDimension; virtual; + procedure GetNativeClipboardFormats(var Formats: TFormatEtcArray); virtual; + function GetOperationCanceled: Boolean; + function GetOptionsClass: TTreeOptionsClass; virtual; + function GetSelectedCount(): Integer; override; + procedure HandleHotTrack(X, Y: TDimension); virtual; + procedure HandleIncrementalSearch(CharCode: Word); virtual; + procedure HandleMouseDblClick(var Message: TWMMouse; const HitInfo: THitInfo); virtual; + procedure HandleMouseDown(var Message: TWMMouse; var HitInfo: THitInfo); virtual; + procedure HandleMouseUp(var Message: TWMMouse; const HitInfo: THitInfo); virtual; + procedure HandleClickSelection(LastFocused, NewNode: PVirtualNode; Shift: TShiftState; DragPending: Boolean); + function HasImage(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex): Boolean; virtual; deprecated 'Use GetImageSize instead'; + function HasPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Pos: TPoint): Boolean; virtual; + procedure IncVisibleCount; + procedure InitChildren(Node: PVirtualNode); virtual; + procedure InitNode(Node: PVirtualNode); virtual; + procedure InternalAddFromStream(Stream: TStream; Version: Integer; Node: PVirtualNode); virtual; + function InternalAddToSelection(Node: PVirtualNode; ForceInsert: Boolean): Boolean; overload; + function InternalAddToSelection(const NewItems: TNodeArray; NewLength: Integer; + ForceInsert: Boolean): Boolean; overload; + procedure InternalCacheNode(Node: PVirtualNode); virtual; + procedure InternalClearSelection; virtual; + procedure InternalConnectNode(Node, Destination: PVirtualNode; Target: TBaseVirtualTree; Mode: TVTNodeAttachMode); virtual; + function InternalData(Node: PVirtualNode): Pointer; + procedure InternalDisconnectNode(Node: PVirtualNode; KeepFocus: Boolean; Reindex: Boolean = True; ParentClearing: Boolean = False); virtual; + procedure InternalSetFocusedColumn(const index : TColumnIndex); + procedure InternalRemoveFromSelection(Node: PVirtualNode); virtual; + procedure InterruptValidation(pWaitForValidationTermination: Boolean = True); + procedure InvalidateCache; + function LineWidth(): TDimension; + procedure Loaded; override; + procedure MainColumnChanged; virtual; + procedure MarkCutCopyNodes; override; + procedure MouseMove(Shift: TShiftState; X, Y: TDimension); override; + procedure Notification(AComponent: TComponent; Operation: TOperation); override; + procedure OriginalWMNCPaint(DC: HDC); virtual; + procedure Paint; override; + procedure PaintCheckImage(Canvas: TCanvas; const ImageInfo: TVTImageInfo; Selected: Boolean); virtual; + procedure PaintImage(var PaintInfo: TVTPaintInfo; ImageInfoIndex: TVTImageInfoIndex; DoOverlay: Boolean); virtual; + procedure PaintNodeButton(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const R: TRect; ButtonX, + ButtonY: TDimension; BidiMode: TBiDiMode); virtual; + procedure PaintTreeLines(const PaintInfo: TVTPaintInfo; IndentSize: TDimension; const LineImage: TLineImage); virtual; + procedure PaintSelectionRectangle(Target: TCanvas; WindowOrgX: TDimension; const SelectionRect: TRect; + TargetRect: TRect); virtual; + procedure PrepareBitmaps(NeedButtons, NeedLines: Boolean); + procedure PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: TDimension); virtual; + function ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType, + ChunkSize: Integer): Boolean; virtual; + procedure ReadNode(Stream: TStream; Version: Integer; Node: PVirtualNode); virtual; + procedure RedirectFontChangeEvent(Canvas: TCanvas); virtual; + procedure RemoveFromSelection(Node: PVirtualNode); virtual; + procedure UpdateNextNodeToSelect(Node: PVirtualNode); virtual; + procedure ResetRangeAnchor; virtual; + procedure RestoreFontChangeEvent(Canvas: TCanvas); virtual; + procedure SelectNodes(StartNode, EndNode: PVirtualNode; AddOnly: Boolean); virtual; + procedure SetChildCount(Node: PVirtualNode; NewChildCount: Cardinal); virtual; + procedure SetFocusedNodeAndColumn(Node: PVirtualNode; Column: TColumnIndex); virtual; + procedure SetRangeX(value: TDimension); + procedure SetWindowTheme(const Theme: string); override; + procedure SetVisibleCount(value : Cardinal); + procedure SkipNode(Stream: TStream); virtual; + procedure StartOperation(OperationKind: TVTOperationKind); + procedure StartWheelPanning(Position: TPoint); virtual; + procedure StopTimer(ID: Integer); + procedure StopWheelPanning; virtual; + procedure StructureChange(Node: PVirtualNode; Reason: TChangeReason); virtual; + function SuggestDropEffect(Source: TObject; Shift: TShiftState; Pt: TPoint; AllowedEffects: Integer): Integer; virtual; + procedure ToggleSelection(StartNode, EndNode: PVirtualNode); virtual; + procedure TrySetFocus(); + procedure UnselectNodes(StartNode, EndNode: PVirtualNode); virtual; + procedure UpdateColumnCheckState(Col: TVirtualTreeColumn); + procedure UpdateDesigner; virtual; + procedure UpdateEditBounds; virtual; + procedure UpdateHeaderRect; virtual; + procedure UpdateStyleElements; override; + procedure ValidateCache; virtual; + procedure ValidateNodeDataSize(var Size: Integer); virtual; + procedure WndProc(var Message: TMessage); override; + procedure WriteChunks(Stream: TStream; Node: PVirtualNode); virtual; + procedure WriteNode(Stream: TStream; Node: PVirtualNode); override; + class procedure RaiseVTError(const Msg: string; HelpContext: Integer); static; + + procedure VclStyleChanged; virtual; + property VclStyleEnabled: Boolean read GetVclStyleEnabled; + property TotalInternalDataSize: Cardinal read FTotalInternalDataSize; + // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) + function StyleServices(AControl: TControl = nil): TCustomStyleServices; + property Alignment: TAlignment read FAlignment write SetAlignment default taLeftJustify; + property AnimationDuration: Cardinal read FAnimationDuration write SetAnimationDuration default 200; + property AutoExpandDelay: Cardinal read FAutoExpandDelay write FAutoExpandDelay default 1000; + property AutoScrollDelay: Cardinal read FAutoScrollDelay write FAutoScrollDelay default 1000; + property AutoScrollInterval: TAutoScrollInterval read FAutoScrollInterval write FAutoScrollInterval default 1; + property Background: TVTBackground read FBackground write SetBackground; + property BackGroundImageTransparent: Boolean read FBackGroundImageTransparent write SetBackGroundImageTransparent default False; + property BackgroundOffsetX: TDimension index 0 read FBackgroundOffsetX write SetBackgroundOffset stored IsStored_BackgroundOffsetXY; // default 0; + property BackgroundOffsetY: TDimension index 1 read FBackgroundOffsetY write SetBackgroundOffset stored IsStored_BackgroundOffsetXY; // default 0; + property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle default TFormBorderStyle.bsSingle; + property BottomSpace: TDimension read FBottomSpace write SetBottomSpace stored IsStored_BottomSpace; //default 0; + property ButtonFillMode: TVTButtonFillMode read FButtonFillMode write SetButtonFillMode default fmTreeColor; + property ButtonStyle: TVTButtonStyle read FButtonStyle write SetButtonStyle default bsRectangle; + property ChangeDelay: Cardinal read FChangeDelay write FChangeDelay default 0; + property CheckImageKind: TCheckImageKind read FCheckImageKind write SetCheckImageKind stored False default ckSystemDefault; // deprecated, see issue #622 + property ClipboardFormats: TClipboardFormats read FClipboardFormats write SetClipboardFormats; + property Colors: TVTColors read FColors write SetColors; + property CustomCheckImages: TCustomImageList read FCustomCheckImages write SetCustomCheckImages; + property DefaultHintKind: TVTHintKind read GetDefaultHintKind; + property DefaultNodeHeight: TDimension read FDefaultNodeHeight write SetDefaultNodeHeight stored IsStored_DefaultNodeHeight; + property DefaultPasteMode: TVTNodeAttachMode read FDefaultPasteMode write FDefaultPasteMode default amAddChildLast; + property DragHeight: Integer read FDragHeight write FDragHeight default 350; + property DragImageKind: TVTDragImageKind read FDragImageKind write FDragImageKind default diComplete; + property DragOperations: TDragOperations read FDragOperations write FDragOperations default [doCopy, doMove]; + property DragSelection: TNodeArray read FDragSelection; + property LastDragEffect: Integer read FLastDragEffect; + property DragType: TVTDragType read FDragType write FDragType default dtOLE; + property DragWidth: Integer read FDragWidth write FDragWidth default 200; + property DrawSelectionMode: TVTDrawSelectionMode read FDrawSelectionMode write FDrawSelectionMode + default smDottedRectangle; + property EditColumn: TColumnIndex read FEditColumn write FEditColumn; + property EditDelay: Cardinal read FEditDelay write FEditDelay default 1000; + property EffectiveOffsetX: TDimension read FEffectiveOffsetX; + property HeaderRect: TRect read FHeaderRect; + property HintMode: TVTHintMode read FHintMode write FHintMode default hmDefault; + property HintData: TVTHintData read FHintData write FHintData; + property HotCursor: TCursor read FHotCursor write FHotCursor default crDefault; + property Images: TCustomImageList read FImages write SetImages; + property IncrementalSearch: TVTIncrementalSearch read FIncrementalSearch write SetSearchOption default isNone; + property IncrementalSearchDirection: TVTSearchDirection read FSearchDirection write FSearchDirection default sdForward; + property IncrementalSearchStart: TVTSearchStart read FSearchStart write FSearchStart default ssFocusedNode; + property IncrementalSearchTimeout: Cardinal read FSearchTimeout write FSearchTimeout default 1000; + property Indent: TDimension read FIndent write SetIndent stored IsStored_Indent; // default 18; + property LastClickPos: TPoint read FLastClickPos write FLastClickPos; + property LastDropMode: TDropMode read FLastDropMode write FLastDropMode; + property LastHintRect: TRect read FLastHintRect write FLastHintRect; + property LineMode: TVTLineMode read FLineMode write SetLineMode default lmNormal; + property LineStyle: TVTLineStyle read FLineStyle write SetLineStyle default lsDotted; + property Margin: TDimension read FMargin write SetMargin stored IsStored_Margin; // default 4; + property NextNodeToSelect: PVirtualNode read FNextNodeToSelect; // Next tree node that we would like to select if the current one gets deleted + property NodeAlignment: TVTNodeAlignment read FNodeAlignment write SetNodeAlignment default naProportional; + property NodeDataSize: Integer read FNodeDataSize write SetNodeDataSize default -1; + property OperationCanceled: Boolean read GetOperationCanceled; + property HotMinusBM: TBitmap read FHotMinusBM; + property HotPlusBM: TBitmap read FHotPlusBM; + property MinusBM: TBitmap read FMinusBM; + property PlusBM: TBitmap read FPlusBM; + property RangeX: TDimension read GetRangeX;// Returns the width of the virtual tree in pixels, (not ClientWidth). If there are columns it returns the total width of all of them; otherwise it returns the maximum of the all the line's data widths. + property RangeY: TNodeHeight read FRangeY; + property RootNodeCount: Cardinal read GetRootNodeCount write SetRootNodeCount default 0; + property ScrollBarOptions: TScrollBarOptions read FScrollBarOptions write SetScrollBarOptions; + property SelectionBlendFactor: Byte read FSelectionBlendFactor write FSelectionBlendFactor default 128; + property SelectionCurveRadius: Cardinal read FSelectionCurveRadius write SetSelectionCurveRadius default 0; + property StateImages: TCustomImageList read FStateImages write SetStateImages; + property TextMargin: TDimension read FTextMargin write SetTextMargin stored IsStored_TextMargin; + property TreeOptions: TCustomVirtualTreeOptions read FOptions write SetOptions; + property WantTabs: Boolean read FWantTabs write FWantTabs default False; + property SyncCheckstateWithSelection[Node: PVirtualNode]: Boolean read GetSyncCheckstateWithSelection; + + property OnAddToSelection: TVTAddToSelectionEvent read FOnAddToSelection write FOnAddToSelection; + property OnAdvancedHeaderDraw: TVTAdvancedHeaderPaintEvent read FOnAdvancedHeaderDraw write FOnAdvancedHeaderDraw; + property OnAfterAutoFitColumn: TVTAfterAutoFitColumnEvent read FOnAfterAutoFitColumn write FOnAfterAutoFitColumn; + property OnAfterAutoFitColumns: TVTAfterAutoFitColumnsEvent read FOnAfterAutoFitColumns write FOnAfterAutoFitColumns; + property OnAfterCellPaint: TVTAfterCellPaintEvent read FOnAfterCellPaint write FOnAfterCellPaint; + property OnAfterColumnExport : TVTColumnExportEvent read FOnAfterColumnExport write FOnAfterColumnExport; + property OnAfterColumnWidthTracking: TVTAfterColumnWidthTrackingEvent read FOnAfterColumnWidthTracking write FOnAfterColumnWidthTracking; + property OnAfterGetMaxColumnWidth: TVTAfterGetMaxColumnWidthEvent read FOnAfterGetMaxColumnWidth write FOnAfterGetMaxColumnWidth; + property OnAfterHeaderExport: TVTTreeExportEvent read FOnAfterHeaderExport write FOnAfterHeaderExport; + property OnAfterHeaderHeightTracking: TVTAfterHeaderHeightTrackingEvent read FOnAfterHeaderHeightTracking + write FOnAfterHeaderHeightTracking; + property OnAfterItemErase: TVTAfterItemEraseEvent read FOnAfterItemErase write FOnAfterItemErase; + property OnAfterItemPaint: TVTAfterItemPaintEvent read FOnAfterItemPaint write FOnAfterItemPaint; + property OnAfterNodeExport: TVTNodeExportEvent read FOnAfterNodeExport write FOnAfterNodeExport; + property OnAfterPaint: TVTPaintEvent read FOnAfterPaint write FOnAfterPaint; + property OnAfterTreeExport: TVTTreeExportEvent read FOnAfterTreeExport write FOnAfterTreeExport; + property OnBeforeAutoFitColumn: TVTBeforeAutoFitColumnEvent read FOnBeforeAutoFitColumn write FOnBeforeAutoFitColumn; + property OnBeforeAutoFitColumns: TVTBeforeAutoFitColumnsEvent read FOnBeforeAutoFitColumns write FOnBeforeAutoFitColumns; + property OnBeforeCellPaint: TVTBeforeCellPaintEvent read FOnBeforeCellPaint write FOnBeforeCellPaint; + property OnBeforeColumnExport: TVTColumnExportEvent read FOnBeforeColumnExport write FOnBeforeColumnExport; + property OnBeforeColumnWidthTracking: TVTBeforeColumnWidthTrackingEvent read FOnBeforeColumnWidthTracking + write FOnBeforeColumnWidthTracking; + property OnBeforeDrawTreeLine: TVTBeforeDrawLineImageEvent read FOnBeforeDrawLineImage write FOnBeforeDrawLineImage; + property OnBeforeGetMaxColumnWidth: TVTBeforeGetMaxColumnWidthEvent read FOnBeforeGetMaxColumnWidth write FOnBeforeGetMaxColumnWidth; + property OnBeforeHeaderExport: TVTTreeExportEvent read FOnBeforeHeaderExport write FOnBeforeHeaderExport; + property OnBeforeHeaderHeightTracking: TVTBeforeHeaderHeightTrackingEvent read FOnBeforeHeaderHeightTracking + write FOnBeforeHeaderHeightTracking; + property OnBeforeItemErase: TVTBeforeItemEraseEvent read FOnBeforeItemErase write FOnBeforeItemErase; + property OnBeforeItemPaint: TVTBeforeItemPaintEvent read FOnBeforeItemPaint write FOnBeforeItemPaint; + property OnBeforeNodeExport: TVTNodeExportEvent read FOnBeforeNodeExport write FOnBeforeNodeExport; + property OnBeforePaint: TVTPaintEvent read FOnBeforePaint write FOnBeforePaint; + property OnBeforeTreeExport: TVTTreeExportEvent read FOnBeforeTreeExport write FOnBeforeTreeExport; + property OnCanSplitterResizeColumn: TVTCanSplitterResizeColumnEvent read FOnCanSplitterResizeColumn write FOnCanSplitterResizeColumn; + property OnCanSplitterResizeHeader: TVTCanSplitterResizeHeaderEvent read FOnCanSplitterResizeHeader write FOnCanSplitterResizeHeader; + property OnCanSplitterResizeNode: TVTCanSplitterResizeNodeEvent read FOnCanSplitterResizeNode write FOnCanSplitterResizeNode; + property OnChange: TVTChangeEvent read FOnChange write FOnChange; + property OnChecked: TVTChangeEvent read FOnChecked write FOnChecked; + property OnChecking: TVTCheckChangingEvent read FOnChecking write FOnChecking; + property OnCollapsed: TVTChangeEvent read FOnCollapsed write FOnCollapsed; + property OnCollapsing: TVTChangingEvent read FOnCollapsing write FOnCollapsing; + property OnColumnChecked: TVTHeaderNotifyEvent read FOnColumnChecked write FOnColumnChecked; + property OnColumnChecking: TVTColumnCheckChangingEvent read FOnColumnChecking write FOnColumnChecking; + property OnColumnClick: TVTColumnClickEvent read FOnColumnClick write FOnColumnClick; + property OnColumnDblClick: TVTColumnDblClickEvent read FOnColumnDblClick write FOnColumnDblClick; + property OnColumnExport : TVTColumnExportEvent read FOnColumnExport write FOnColumnExport; + property OnColumnResize: TVTHeaderNotifyEvent read FOnColumnResize write FOnColumnResize; + property OnColumnVisibilityChanged: TColumnChangeEvent read fOnColumnVisibilityChanged write fOnColumnVisibilityChanged; + property OnColumnWidthDblClickResize: TVTColumnWidthDblClickResizeEvent read FOnColumnWidthDblClickResize + write FOnColumnWidthDblClickResize; + property OnColumnWidthTracking: TVTColumnWidthTrackingEvent read FOnColumnWidthTracking write FOnColumnWidthTracking; + property OnCompareNodes: TVTCompareEvent read FOnCompareNodes write FOnCompareNodes; + property OnCreateDataObject: TVTCreateDataObjectEvent read FOnCreateDataObject write FOnCreateDataObject; + property OnCreateDragManager: TVTCreateDragManagerEvent read FOnCreateDragManager write FOnCreateDragManager; + property OnCreateEditor: TVTCreateEditorEvent read FOnCreateEditor write FOnCreateEditor; + property OnDragAllowed: TVTDragAllowedEvent read FOnDragAllowed write FOnDragAllowed; + property OnDragOver: TVTDragOverEvent read FOnDragOver write FOnDragOver; + property OnDragDrop: TVTDragDropEvent read FOnDragDrop write FOnDragDrop; + property OnDrawHint: TVTDrawHintEvent read FOnDrawHint write FOnDrawHint; + property OnEditCancelled: TVTEditCancelEvent read FOnEditCancelled write FOnEditCancelled; + property OnEditing: TVTEditChangingEvent read FOnEditing write FOnEditing; + property OnEdited: TVTEditChangeEvent read FOnEdited write FOnEdited; + property OnEndOperation: TVTOperationEvent read FOnEndOperation write FOnEndOperation; + property OnExpanded: TVTChangeEvent read FOnExpanded write FOnExpanded; + property OnExpanding: TVTChangingEvent read FOnExpanding write FOnExpanding; + property OnFocusChanged: TVTFocusChangeEvent read FOnFocusChanged write FOnFocusChanged; + property OnFocusChanging: TVTFocusChangingEvent read FOnFocusChanging write FOnFocusChanging; + property OnFreeNode: TVTFreeNodeEvent read FOnFreeNode write FOnFreeNode; + property OnGetCellIsEmpty: TVTGetCellIsEmptyEvent read FOnGetCellIsEmpty write FOnGetCellIsEmpty; + property OnGetCursor: TVTGetCursorEvent read FOnGetCursor write FOnGetCursor; + property OnGetHeaderCursor: TVTGetHeaderCursorEvent read FOnGetHeaderCursor write FOnGetHeaderCursor; + property OnGetHelpContext: TVTHelpContextEvent read FOnGetHelpContext write FOnGetHelpContext; + property OnGetHintSize: TVTGetHintSizeEvent read FOnGetHintSize write + FOnGetHintSize; + property OnGetHintKind: TVTHintKindEvent read FOnGetHintKind write + FOnGetHintKind; + property OnGetImageIndex: TVTGetImageEvent read FOnGetImage write FOnGetImage; + property OnGetImageIndexEx: TVTGetImageExEvent read FOnGetImageEx write FOnGetImageEx; + property OnGetImageText: TVTGetImageTextEvent read FOnGetImageText write FOnGetImageText; + property OnGetLineStyle: TVTGetLineStyleEvent read FOnGetLineStyle write FOnGetLineStyle; + property OnGetNodeDataSize: TVTGetNodeDataSizeEvent read FOnGetNodeDataSize write FOnGetNodeDataSize; + property OnGetPopupMenu: TVTPopupEvent read FOnGetPopupMenu write FOnGetPopupMenu; + property OnGetUserClipboardFormats: TVTGetUserClipboardFormatsEvent read FOnGetUserClipboardFormats + write FOnGetUserClipboardFormats; + property OnHeaderAddPopupItem: TVTHeaderAddPopupItemEvent read FOnHeaderAddPopupItem write FOnHeaderAddPopupItem; + property OnHeaderClick: TVTHeaderClickEvent read FOnHeaderClick write FOnHeaderClick; + property OnHeaderDblClick: TVTHeaderClickEvent read FOnHeaderDblClick write FOnHeaderDblClick; + property OnHeaderDragged: TVTHeaderDraggedEvent read FOnHeaderDragged write FOnHeaderDragged; + property OnHeaderDraggedOut: TVTHeaderDraggedOutEvent read FOnHeaderDraggedOut write FOnHeaderDraggedOut; + property OnHeaderDragging: TVTHeaderDraggingEvent read FOnHeaderDragging write FOnHeaderDragging; + property OnHeaderDraw: TVTHeaderPaintEvent read FOnHeaderDraw write FOnHeaderDraw; + property OnHeaderDrawQueryElements: TVTHeaderPaintQueryElementsEvent read FOnHeaderDrawQueryElements + write FOnHeaderDrawQueryElements; + property OnHeaderHeightTracking: TVTHeaderHeightTrackingEvent read FOnHeaderHeightTracking + write FOnHeaderHeightTracking; + property OnHeaderHeightDblClickResize: TVTHeaderHeightDblClickResizeEvent read FOnHeaderHeightDblClickResize + write FOnHeaderHeightDblClickResize; + property OnHeaderMouseDown: TVTHeaderMouseEvent read FOnHeaderMouseDown write FOnHeaderMouseDown; + property OnHeaderMouseMove: TVTHeaderMouseMoveEvent read FOnHeaderMouseMove write FOnHeaderMouseMove; + property OnHeaderMouseUp: TVTHeaderMouseEvent read FOnHeaderMouseUp write FOnHeaderMouseUp; + property OnHotChange: TVTHotNodeChangeEvent read FOnHotChange write FOnHotChange; + property OnIncrementalSearch: TVTIncrementalSearchEvent read FOnIncrementalSearch write FOnIncrementalSearch; + property OnInitChildren: TVTInitChildrenEvent read FOnInitChildren write FOnInitChildren; + property OnInitNode: TVTInitNodeEvent read FOnInitNode write FOnInitNode; + property OnKeyAction: TVTKeyActionEvent read FOnKeyAction write FOnKeyAction; + property OnLoadNode: TVTSaveNodeEvent read FOnLoadNode write FOnLoadNode; + property OnLoadTree: TVTSaveTreeEvent read FOnLoadTree write FOnLoadTree; + property OnMeasureItem: TVTMeasureItemEvent read FOnMeasureItem write FOnMeasureItem; + property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter; + property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave; + property OnNodeClick: TVTNodeClickEvent read FOnNodeClick write FOnNodeClick; + property OnNodeCopied: TVTNodeCopiedEvent read FOnNodeCopied write FOnNodeCopied; + property OnNodeCopying: TVTNodeCopyingEvent read FOnNodeCopying write FOnNodeCopying; + property OnNodeDblClick: TVTNodeClickEvent read FOnNodeDblClick write FOnNodeDblClick; + property OnNodeExport: TVTNodeExportEvent read FOnNodeExport write FOnNodeExport; + property OnNodeHeightTracking: TVTNodeHeightTrackingEvent read FOnNodeHeightTracking write FOnNodeHeightTracking; + property OnNodeHeightDblClickResize: TVTNodeHeightDblClickResizeEvent read FOnNodeHeightDblClickResize + write FOnNodeHeightDblClickResize; + property OnNodeMoved: TVTNodeMovedEvent read FOnNodeMoved write FOnNodeMoved; + property OnNodeMoving: TVTNodeMovingEvent read FOnNodeMoving write FOnNodeMoving; + property OnPaintBackground: TVTBackgroundPaintEvent read FOnPaintBackground write FOnPaintBackground; + property OnPaintText: TVTPaintText read FOnPaintText write FOnPaintText; + property OnPrepareButtonBitmaps : TVTPrepareButtonImagesEvent read FOnPrepareButtonImages write SetOnPrepareButtonImages; + property OnRemoveFromSelection: TVTRemoveFromSelectionEvent read FOnRemoveFromSelection write FOnRemoveFromSelection; + property OnResetNode: TVTChangeEvent read FOnResetNode write FOnResetNode; + property OnSaveNode: TVTSaveNodeEvent read FOnSaveNode write FOnSaveNode; + property OnSaveTree: TVTSaveTreeEvent read FOnSaveTree write FOnSaveTree; + property OnScroll: TVTScrollEvent read FOnScroll write FOnScroll; + property OnShowScrollBar: TVTScrollBarShowEvent read FOnShowScrollBar write FOnShowScrollBar; + property OnBeforeGetCheckState: TVTBeforeGetCheckStateEvent read FOnBeforeGetCheckState write FOnBeforeGetCheckState; + property OnStartOperation: TVTOperationEvent read FOnStartOperation write FOnStartOperation; + property OnStateChange: TVTStateChangeEvent read FOnStateChange write FOnStateChange; + property OnStructureChange: TVTStructureChangeEvent read FOnStructureChange write FOnStructureChange; + property OnUpdating: TVTUpdatingEvent read FOnUpdating write FOnUpdating; + property OnColumnHeaderSpanning: TVTColumnHeaderSpanningEvent read FOnColumnHeaderSpanning write FOnColumnHeaderSpanning; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + function AbsoluteIndex(Node: PVirtualNode): Cardinal; + function AddChild(Parent: PVirtualNode; UserData: Pointer = nil): PVirtualNode; overload; virtual; + function AddChild(Parent: PVirtualNode; const UserData: IInterface): PVirtualNode; overload; + function AddChild(Parent: PVirtualNode; const UserData: TObject): PVirtualNode; overload; + procedure AddFromStream(Stream: TStream; TargetNode: PVirtualNode); + procedure AddToSelection(Node: PVirtualNode; NotifySynced: Boolean); overload; virtual; + procedure AfterConstruction; override; + procedure Assign(Source: TPersistent); override; + procedure BeginDrag(Immediate: Boolean; Threshold: Integer = -1); + procedure BeginSynch; + procedure BeginUpdate; virtual; + procedure CancelCutOrCopy; + function CancelEditNode: Boolean; + procedure CancelOperation; + function CanEdit(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; + function CanFocus: Boolean; override; + procedure Clear; virtual; + procedure ClearChecked; + procedure ClearSelection(); overload; inline; + function CopyTo(Source: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode; + ChildrenOnly: Boolean): PVirtualNode; overload; + function CopyTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode; + ChildrenOnly: Boolean): PVirtualNode; overload; + procedure CutToClipboard(); override; + procedure DeleteChildren(Node: PVirtualNode; ResetHasChildren: Boolean = False); + procedure DeleteNode(Node: PVirtualNode; pReIndex: Boolean = True); overload; inline; + procedure DeleteNodes(const pNodes: TNodeArray); + procedure DeleteSelectedNodes; virtual; + function Dragging: Boolean; + procedure DrawGridLine(Canvas: TCanvas; R: TRect); virtual; + function EditNode(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; + function EndEditNode: Boolean; + procedure EndSynch; + procedure EndUpdate; virtual; + procedure EnsureNodeSelected(pAfterDeletion: Boolean); virtual; + function ExecuteAction(Action: TBasicAction): Boolean; override; + procedure FinishCutOrCopy; + procedure FlushClipboard; + procedure FullCollapse(Node: PVirtualNode = nil); virtual; + procedure FullExpand(Node: PVirtualNode = nil); virtual; + function GetControlsAlignment: TAlignment; override; + function GetDisplayRect(Node: PVirtualNode; Column: TColumnIndex; TextOnly: Boolean; Unclipped: Boolean = False; + ApplyCellContentMargin: Boolean = False): TRect; + function GetEffectivelyFiltered(Node: PVirtualNode): Boolean; + function GetEffectivelyVisible(Node: PVirtualNode): Boolean; + function GetFirst(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetFirstChecked(State: TCheckState = csCheckedNormal; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetFirstChild(Node: PVirtualNode): PVirtualNode; + function GetFirstChildNoInit(Node: PVirtualNode): PVirtualNode; + function GetFirstCutCopy(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetFirstInitialized(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetFirstLeaf: PVirtualNode; + function GetFirstLevel(NodeLevel: Cardinal): PVirtualNode; + function GetFirstNoInit(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetFirstSelected(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetFirstVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; + IncludeFiltered: Boolean = False): PVirtualNode; + function GetFirstVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + function GetFirstVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + function GetFirstVisibleNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; + IncludeFiltered: Boolean = False): PVirtualNode; + procedure GetHitTestInfoAt(X, Y: TDimension; Relative: Boolean; var HitInfo: THitInfo; ShiftState: TShiftState=[]); virtual; + function GetLast(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetLastInitialized(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetLastNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetLastChild(Node: PVirtualNode): PVirtualNode; + function GetLastChildNoInit(Node: PVirtualNode): PVirtualNode; + function GetLastSelected(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetLastVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; + IncludeFiltered: Boolean = False): PVirtualNode; + function GetLastVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + function GetLastVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + function GetLastVisibleNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; + IncludeFiltered: Boolean = False): PVirtualNode; + function GetMaxColumnWidth(Column: TColumnIndex; UseSmartColumnWidth: Boolean = False): TDimension; virtual; + function GetNext(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetNextChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal; + ConsiderChildrenAbove: Boolean = False): PVirtualNode; overload; + function GetNextChecked(Node: PVirtualNode; ConsiderChildrenAbove: Boolean): PVirtualNode; overload; + function GetNextCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetNextInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetNextLeaf(Node: PVirtualNode): PVirtualNode; + function GetNextLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode; + function GetNextNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetNextSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetNextSibling(Node: PVirtualNode): PVirtualNode; + function GetNextSiblingNoInit(Node: PVirtualNode): PVirtualNode; + function GetNextVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; + function GetNextVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; + function GetNextVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + function GetNextVisibleSiblingNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + function GetNodeAt(const P: TPoint): PVirtualNode; overload; inline; + function GetNodeAt(X, Y: TDimension): PVirtualNode; overload; + function GetNodeAt(X, Y: TDimension; Relative: Boolean; var NodeTop: TDimension): PVirtualNode; overload; + function GetNodeData(Node: PVirtualNode): Pointer; overload; + function GetNodeData(pNode: PVirtualNode): T; overload; inline; + function GetSelectedData(): TArray; overload; + function GetInterfaceFromNodeData(pNode: PVirtualNode): T; overload; inline; + function GetNodeDataAt(pXCoord: Integer; pYCoord: Integer): T; + function GetFirstSelectedNodeData(): T; + function GetNodeLevel(Node: PVirtualNode): Cardinal; + function GetNodeLevelForSelectConstraint(Node: PVirtualNode): integer; + function GetOffset(pElement: TVTElement; pNode: PVirtualNode): TDimension; + procedure GetOffsets(pNode: PVirtualNode; out pOffsets: TVTOffsets; pElement: TVTElement = TVTElement.ofsEndOfClientArea; pColumn: Integer = NoColumn); + function GetPrevious(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetPreviousChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal; + ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetPreviousCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetPreviousInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetPreviousLeaf(Node: PVirtualNode): PVirtualNode; + function GetPreviousLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode; + function GetPreviousNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetPreviousSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + function GetPreviousSibling(Node: PVirtualNode): PVirtualNode; + function GetPreviousSiblingNoInit(Node: PVirtualNode): PVirtualNode; + function GetPreviousVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; + function GetPreviousVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; + function GetPreviousVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + function GetPreviousVisibleSiblingNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + function GetSortedCutCopySet(Resolve: Boolean): TNodeArray; override; + function GetSortedSelection(Resolve: Boolean): TNodeArray; override; + procedure GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect; + var Text: string); virtual; + function GetTreeRect: TRect; + function GetVisibleParent(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + function GetTopInvisibleParent(Node: PVirtualNode): PVirtualNode; + function HasAsParent(Node, PotentialParent: PVirtualNode): Boolean; + function InsertNode(Node: PVirtualNode; Mode: TVTNodeAttachMode; UserData: Pointer = nil): PVirtualNode; + procedure InvalidateChildren(Node: PVirtualNode; Recursive: Boolean); + procedure InvalidateColumn(Column: TColumnIndex); + function InvalidateNode(Node: PVirtualNode): TRect; virtual; + procedure InvalidateToBottom(Node: PVirtualNode); + procedure InvertSelection(VisibleOnly: Boolean); + function IsEditing: Boolean; + function IsMouseSelecting: Boolean; + function IsEmpty: Boolean; inline; + function IsUpdating(): Boolean; + function IterateSubtree(StartNode: PVirtualNode; Callback: TVTGetNodeProc; Data: Pointer; Filter: TVirtualNodeStates = []; + DoInit: Boolean = False; ChildNodesOnly: Boolean = False): PVirtualNode; + procedure LoadFromFile(const FileName: TFileName); virtual; + procedure LoadFromStream(Stream: TStream); virtual; + procedure MeasureItemHeight(const Canvas: TCanvas; Node: PVirtualNode); virtual; + procedure MoveTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode; ChildrenOnly: Boolean); overload; + procedure MoveTo(Node: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode; + ChildrenOnly: Boolean); overload; + procedure PaintTree(TargetCanvas: TCanvas; Window: TRect; Target: TPoint; PaintOptions: TVTInternalPaintOptions; + PixelFormat: TPixelFormat = pfDevice); virtual; + procedure PrepareDragImage(HotSpot: TPoint; const DataObject: TVTDragDataObject); + procedure Print(Printer: TPrinter; PrintHeader: Boolean); + function ProcessDrop(const DataObject: TVTDragDataObject; TargetNode: PVirtualNode; var Effect: Integer; Mode: + TVTNodeAttachMode): Boolean; + function ProcessOLEData(Source: TBaseVirtualTree; const DataObject: IDataObject; TargetNode: PVirtualNode; + Mode: TVTNodeAttachMode; Optimized: Boolean): Boolean; + procedure RepaintNode(Node: PVirtualNode); + procedure ReinitChildren(Node: PVirtualNode; Recursive: Boolean; ForceReinit: Boolean = False); virtual; + procedure InitRecursive(Node: PVirtualNode; Levels: Cardinal = MaxInt; pVisibleOnly: Boolean = True); + procedure ReinitNode(Node: PVirtualNode; Recursive: Boolean; ForceReinit: Boolean = False); virtual; + procedure ResetNode(Node: PVirtualNode); virtual; + procedure SaveToFile(const FileName: TFileName); + procedure SaveToStream(Stream: TStream; Node: PVirtualNode = nil); virtual; + function ScaledPixels(pPixels: TDimension): TDimension; + procedure ScaleNodeHeights(M, D: TDimension); + function ScrollIntoView(Node: PVirtualNode; Center: Boolean; Horizontally: Boolean = False): Boolean; overload; + function ScrollIntoView(Column: TColumnIndex; Center: Boolean; Node: PVirtualNode = nil): Boolean; overload; + procedure SelectAll(VisibleOnly: Boolean); + procedure SetCheckStateForAll(aCheckState: TCheckState; pSelectedOnly: Boolean; pExcludeDisabled: Boolean = True); + procedure SetNodeData(pNode: PVirtualNode; pUserData: Pointer); overload; inline; + procedure SetNodeData(pNode: PVirtualNode; const pUserData: IInterface); overload; inline; + procedure SetNodeData(pNode: PVirtualNode; pUserData: T); overload; + procedure Sort(Node: PVirtualNode; Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); override; + procedure SortTree(Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); virtual; + procedure ToggleNode(Node: PVirtualNode); + procedure UpdateHorizontalRange; virtual; + procedure UpdateHorizontalScrollBar(DoRepaint: Boolean); + procedure UpdateRanges; + procedure UpdateScrollBars(DoRepaint: Boolean); virtual; + procedure UpdateVerticalRange; + procedure UpdateVerticalScrollBar(DoRepaint: Boolean); + function UseRightToLeftReading: Boolean; + procedure ValidateChildren(Node: PVirtualNode; Recursive: Boolean); + procedure ValidateNode(Node: PVirtualNode; Recursive: Boolean); + + { Enumerations } + function Nodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; + function CheckedNodes(State: TCheckState = csCheckedNormal; ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; + function ChildNodes(Node: PVirtualNode): TVTVirtualNodeEnumeration; + function CutCopyNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; + function InitializedNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; + function LeafNodes: TVTVirtualNodeEnumeration; + function LevelNodes(NodeLevel: Cardinal): TVTVirtualNodeEnumeration; + function NoInitNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; + function SelectedNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; + function VisibleNodes(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; + IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration; + function VisibleChildNodes(Node: PVirtualNode; IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration; + function VisibleChildNoInitNodes(Node: PVirtualNode; IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration; + function VisibleNoInitNodes(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; + IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration; + property BottomNode: PVirtualNode read GetBottomNode write SetBottomNode; + property CheckedCount: Integer read GetCheckedCount; + property CheckImages: TCustomImageList read FCheckImages; + property CheckState[Node: PVirtualNode]: TCheckState read GetCheckState write SetCheckState; + property CheckType[Node: PVirtualNode]: TCheckType read GetCheckType write SetCheckType; + property ChildCount[Node: PVirtualNode]: Cardinal read GetChildCount write SetChildCount; + property ChildrenInitialized[Node: PVirtualNode]: Boolean read GetChildrenInitialized; + property CutCopyCount: Integer read GetCutCopyCount; + property DragManager: IVTDragManager read GetDragManager; + property DropTargetNode: PVirtualNode read FDropTargetNode write FDropTargetNode; + property EditLink: IVTEditLink read FEditLink; + property EmptyListMessage: string read FEmptyListMessage write SetEmptyListMessage; + property Expanded[Node: PVirtualNode]: Boolean read GetExpanded write SetExpanded; + property FocusedColumn: TColumnIndex read FFocusedColumn write SetFocusedColumn default InvalidColumn; + property FocusedNode: PVirtualNode read FFocusedNode write SetFocusedNode; + property Font; + property FullyVisible[Node: PVirtualNode]: Boolean read GetFullyVisible write SetFullyVisible; + property HasChildren[Node: PVirtualNode]: Boolean read GetHasChildren write SetHasChildren; + property Header: TVTHeader read FHeader write SetHeader; + property HotNode: PVirtualNode read FCurrentHotNode write SetHotNode; + property HotColumn: TColumnIndex read FCurrentHotColumn; + property IsDisabled[Node: PVirtualNode]: Boolean read GetDisabled write SetDisabled; + property IsEffectivelyFiltered[Node: PVirtualNode]: Boolean read GetEffectivelyFiltered; + property IsEffectivelyVisible[Node: PVirtualNode]: Boolean read GetEffectivelyVisible; + property IsFiltered[Node: PVirtualNode]: Boolean read GetFiltered write SetFiltered; + property IsVisible[Node: PVirtualNode]: Boolean read GetVisible write SetVisible; + property MultiLine[Node: PVirtualNode]: Boolean read GetMultiline write SetMultiline; + property NodeHeight[Node: PVirtualNode]: TNodeHeight read GetNodeHeight write SetNodeHeight; + property NodeParent[Node: PVirtualNode]: PVirtualNode read GetNodeParent write SetNodeParent; + property OffsetX: TDimension read FOffsetX write SetOffsetX; + property OffsetXY: TPoint read GetOffsetXY write SetOffsetXY; + property OffsetY: TDimension read FOffsetY write SetOffsetY; + property OperationCount: Cardinal read FOperationCount; + property RootNode: PVirtualNode read FRoot; + property SearchBuffer: string read FSearchBuffer; + property Selected[Node: PVirtualNode]: Boolean read GetSelected write SetSelected; + property SelectionLocked: Boolean read FSelectionLocked write FSelectionLocked; + property TotalCount: Cardinal read GetTotalCount; + property TreeStates: TVirtualTreeStates read FStates write FStates; + property SelectedCount: Integer read FSelectionCount; + property TopNode: PVirtualNode read GetTopNode write SetTopNode; + property VerticalAlignment[Node: PVirtualNode]: Byte read GetVerticalAlignment write SetVerticalAlignment; + property VisibleCount: Cardinal read FVisibleCount; + property VisiblePath[Node: PVirtualNode]: Boolean read GetVisiblePath write SetVisiblePath; + property UpdateCount: Cardinal read FUpdateCount; + property DoubleBuffered: Boolean read GetDoubleBuffered write SetDoubleBuffered default True; + end; + + TVTDrawNodeEvent = procedure(Sender: TBaseVirtualTree; const PaintInfo: TVTPaintInfo) of object; + TVTGetCellContentMarginEvent = procedure(Sender: TBaseVirtualTree; HintCanvas: TCanvas; Node: PVirtualNode; + Column: TColumnIndex; CellContentMarginType: TVTCellContentMarginType; var CellContentMargin: TPoint) of object; + TVTGetNodeWidthEvent = procedure(Sender: TBaseVirtualTree; HintCanvas: TCanvas; Node: PVirtualNode; + Column: TColumnIndex; var NodeWidth: TDimension) of object; + + +// utility routines +function TreeFromNode(Node: PVirtualNode): TBaseVirtualTree; + + +//---------------------------------------------------------------------------------------------------------------------- + +implementation + +uses + Winapi.MMSystem, // for animation timer (does not include further resources) + System.Math, + System.SyncObjs, + System.StrUtils, + Clipbrd, + Vcl.Consts, + Vcl.ExtCtrls, + Vcl.AxCtrls, // TOLEStream + Vcl.StdActns, // for standard action support + Vcl.GraphUtil, // accessibility helper class + VirtualTrees.StyleHooks, + VirtualTrees.WorkerThread, + VirtualTrees.ClipBoard, + VirtualTrees.Utils, + VirtualTrees.DragnDrop; + +resourcestring + // Localizable strings. + SEditLinkIsNil = 'Edit link must not be nil.'; + SWrongMoveError = 'Target node cannot be a child node of the node to be moved.'; + SWrongStreamFormat = 'Unable to load tree structure, the format is wrong.'; + SWrongStreamVersion = 'Unable to load tree structure, the version is unknown.'; + SStreamTooSmall = 'Unable to load tree structure, not enough data available.'; + SCorruptStream1 = 'Stream data corrupt. A node''s anchor chunk is missing.'; + SCorruptStream2 = 'Stream data corrupt. Unexpected data after node''s end position.'; + +const + ClipboardStates = [tsCopyPending, tsCutPending]; + DefaultScrollUpdateFlags = [suoRepaintHeader, suoRepaintScrollBars, suoScrollClientArea, suoUpdateNCArea]; + TreeNodeSize = (SizeOf(TVirtualNode) + (SizeOf(Pointer) - 1)) and not (SizeOf(Pointer) - 1); // used for node allocation and access to internal data + /// Default value of the DefaultText property + MouseButtonDown = [tsLeftButtonDown, tsMiddleButtonDown, tsRightButtonDown]; + + // Do not modify the copyright in any way! Usage of this unit is prohibited without the copyright notice + // in the compiled binary file. + Copyright: string = 'Virtual Treeview © 1999-2021 Mike Lischke, Joachim Marder'; + + + +type + //These allow us access to protected members in the classes + TVirtualTreeColumnsCracker = class(TVirtualTreeColumns); + TVTHeaderCracker = class(TVTHeader); + TVirtualTreeColumnCracker = class(TVirtualTreeColumn); + TBaseVirtualTreeCracker = class(TBaseVirtualTree); + + // streaming support + TMagicID = array[0..5] of WideChar; + + // base information about a node + TBaseChunkBody = packed record + ChildCount: Cardinal; + NodeHeight: TDimension; + States: TVirtualNodeStates; + Align: Byte; + CheckState: TCheckState; + CheckType: TCheckType; + Reserved: Cardinal; + end; + + TBaseChunk = packed record + Header: TChunkHeader; + Body: TBaseChunkBody; + end; + + // Toggle animation modes. + TToggleAnimationMode = ( + tamScrollUp, + tamScrollDown, + tamNoScroll + ); + + // Internally used data for animations. + TToggleAnimationData = record + Window: HWND; // copy of the tree's window handle + DC: TControlCanvas; // the DC of the window to erase uncovered parts + Brush: TBrush; // the brush to be used to erase uncovered parts + R1, + R2: TRect; // animation rectangles + Mode1, + Mode2: TToggleAnimationMode; // animation modes + ScaleFactor: Double; // the factor between the missing step size when doing two animations + MissedSteps: Double; + end; + + +const + MagicID: TMagicID = (#$2045, 'V', 'T', WideChar(VTTreeStreamVersion), ' ', #$2046); + +var + gWatcher: TCriticalSection = nil; + gInitialized: Integer = 0; // >0 if global structures have been initialized; otherwise 0 + NeedToUnitialize: Boolean = False; // True if the OLE subsystem could be initialized successfully. + +//---------------------------------------------------------------------------------------------------------------------- + +function TreeFromNode(Node: PVirtualNode): TBaseVirtualTree; + +// Returns the tree the node currently belongs to or nil if the node is not attached to a tree. + +begin + Assert(Assigned(Node), 'Node must not be nil.'); + + // The root node is marked by having its NextSibling (and PrevSibling) pointing to itself. + while Assigned(Node) and (Node.NextSibling <> Node) do + Node := Node.Parent; + if Assigned(Node) then + Result := TBaseVirtualTree(Node.Parent) + else + Result := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure QuickSort(const TheArray: TNodeArray; L, R: Integer); + +var + I, J: Integer; + P, T: Pointer; + +begin + repeat + I := L; + J := R; + P := TheArray[(L + R) shr 1]; + repeat + while PAnsiChar(TheArray[I]) < PAnsiChar(P) do + System.Inc(I); + while PAnsiChar(TheArray[J]) > PAnsiChar(P) do + System.Dec(J); + if I <= J then + begin + T := TheArray[I]; + TheArray[I] := TheArray[J]; + TheArray[J] := T; + System.Inc(I); + System.Dec(J); + end; + until I > J; + if L < J then + QuickSort(TheArray, L, J); + L := I; + until I >= R; +end; + +//----------------- TVTVirtualNodeEnumerator --------------------------------------------------------------------------- + +function TVTVirtualNodeEnumerator.GetCurrent: PVirtualNode; + +begin + Result := FNode; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTVirtualNodeEnumerator.MoveNext: Boolean; + +begin + Result := FCanMoveNext; + if Result then + begin + FNode := FEnumeration.GetNext(FNode); + Result := FNode <> nil; + FCanMoveNext := Result; + end; +end; + +//----------------- TVTVirtualNodeEnumeration -------------------------------------------------------------------------- + +function TVTVirtualNodeEnumeration.GetEnumerator: TVTVirtualNodeEnumerator; + +begin + Result.FNode := nil; + Result.FCanMoveNext := True; + Result.FEnumeration := @Self; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTVirtualNodeEnumeration.GetNext(Node: PVirtualNode): PVirtualNode; +begin + case FMode of + vneAll: + if Node = nil then + Result := FTree.GetFirst(FConsiderChildrenAbove) + else + Result := FTree.GetNext(Node, FConsiderChildrenAbove); + + vneChecked: + if Node = nil then + Result := FTree.GetFirstChecked(FState, FConsiderChildrenAbove) + else + Result := FTree.GetNextChecked(Node, FState, FConsiderChildrenAbove); + + vneChild: + if Node = nil then + Result := FTree.GetFirstChild(FNode) + else + Result := FTree.GetNextSibling(Node); + + vneCutCopy: + if Node = nil then + Result := FTree.GetFirstCutCopy(FConsiderChildrenAbove) + else + Result := FTree.GetNextCutCopy(Node, FConsiderChildrenAbove); + + vneInitialized: + if Node = nil then + Result := FTree.GetFirstInitialized(FConsiderChildrenAbove) + else + Result := FTree.GetNextInitialized(Node, FConsiderChildrenAbove); + + vneLeaf: + if Node = nil then + Result := FTree.GetFirstLeaf + else + Result := FTree.GetNextLeaf(Node); + + vneLevel: + if Node = nil then + Result := FTree.GetFirstLevel(FNodeLevel) + else + Result := FTree.GetNextLevel(Node, FNodeLevel); + + vneNoInit: + if Node = nil then + Result := FTree.GetFirstNoInit(FConsiderChildrenAbove) + else + Result := FTree.GetNextNoInit(Node, FConsiderChildrenAbove); + + vneSelected: + if Node = nil then + Result := FTree.GetFirstSelected(FConsiderChildrenAbove) + else + Result := FTree.GetNextSelected(Node, FConsiderChildrenAbove); + + vneVisible: + begin + if Node = nil then + begin + Result := FTree.GetFirstVisible(FNode, FConsiderChildrenAbove, FIncludeFiltered); + if FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result] then + Exit; + end; + repeat + Result := FTree.GetNextVisible(Node{, FConsiderChildrenAbove}); + until not Assigned(Result) or FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result]; + end; + + vneVisibleChild: + if Node = nil then + Result := FTree.GetFirstVisibleChild(FNode, FIncludeFiltered) + else + Result := FTree.GetNextVisibleSibling(Node, FIncludeFiltered); + + vneVisibleNoInitChild: + if Node = nil then + Result := FTree.GetFirstVisibleChildNoInit(FNode, FIncludeFiltered) + else + Result := FTree.GetNextVisibleSiblingNoInit(Node, FIncludeFiltered); + + vneVisibleNoInit: + begin + if Node = nil then + begin + Result := FTree.GetFirstVisibleNoInit(FNode, FConsiderChildrenAbove, FIncludeFiltered); + if FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result] then + Exit; + end; + repeat + Result := FTree.GetNextVisibleNoInit(Node, FConsiderChildrenAbove); + until not Assigned(Result) or FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result]; + end; + else + Result := nil; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure InitializeGlobalStructures(); + +// initialization of stuff global to the unit +begin + if (gInitialized > 0) or (AtomicIncrement(gInitialized) <> 1) then // Ensure threadsafe that this code is executed only once + exit; + + // This watcher is used whenever a global structure could be modified by more than one thread. + gWatcher := TCriticalSection.Create(); + + // Initialize OLE subsystem for drag'n drop and clipboard operations. + NeedToUnitialize := not IsLibrary and Succeeded(OleInitialize(nil)); + + // Register the tree reference clipboard format. + CF_VTREFERENCE := RegisterClipboardFormat(CFSTR_VTREFERENCE); + CF_VTHEADERREFERENCE := RegisterClipboardFormat(CFSTR_VTHEADERREFERENCE); + + // Clipboard format registration. + // Native clipboard format. Needs a new identifier and has an average priority to allow other formats to take over. + // This format is supposed to use the IStream storage format but unfortunately this does not work when + // OLEFlushClipboard is used. Hence it is disabled until somebody finds a solution. + CF_VIRTUALTREE := RegisterVTClipboardFormat(CFSTR_VIRTUALTREE, TBaseVirtualTree, 50, TYMED_HGLOBAL {or TYMED_ISTREAM}); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure FinalizeGlobalStructures(); + +var + HintWasEnabled: Boolean; + +begin + if gInitialized = 0 then + exit; // Was not initialized + + if NeedToUnitialize then + OleUninitialize; + + // If VT is used in a package and its special hint window was used then the last instance of this + // window is not freed correctly (bug in the VCL). We explicitely tell the application to free it + // otherwise an AV is raised due to access to an invalid memory area. + if ModuleIsPackage then + begin + HintWasEnabled := Application.ShowHint; + Application.ShowHint := False; + if HintWasEnabled then + Application.ShowHint := True; + end; + gWatcher.Free; + gWatcher := nil; +end; + +//----------------- TClipboardFormats ---------------------------------------------------------------------------------- + +constructor TClipboardFormats.Create(AOwner: TBaseVirtualTree); + +begin + FOwner := AOwner; + Sorted := True; + Duplicates := dupIgnore; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TClipboardFormats.Add(const S: string): Integer; + +// Restrict additions to the clipbard formats to only those which are registered with the owner tree or one of its +// ancestors. + +var + Format: Word; + RegisteredClass: TVirtualTreeClass; + +begin + RegisteredClass := TClipboardFormatList.FindFormat(S, Format); + if Assigned(RegisteredClass) and FOwner.ClassType.InheritsFrom(RegisteredClass) then + Result := inherited Add(S) + else + Result := -1; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TClipboardFormats.Insert(Index: Integer; const S: string); + +// Restrict additions to the clipbard formats to only those which are registered with the owner tree or one of its +// ancestors. + +var + Format: Word; + RegisteredClass: TVirtualTreeClass; + +begin + RegisteredClass := TClipboardFormatList.FindFormat(S, Format); + if Assigned(RegisteredClass) and FOwner.ClassType.InheritsFrom(RegisteredClass) then + inherited Insert(Index, S); +end; + +//----------------- TBaseVirtualTree ----------------------------------------------------------------------------------- + +constructor TBaseVirtualTree.Create(AOwner: TComponent); + +begin + InitializeGlobalStructures(); + + inherited; + + ControlStyle := ControlStyle - [csSetCaption] + [csCaptureMouse, csOpaque, csReplicatable, csDisplayDragImage, + csReflector]; + FTotalInternalDataSize := 0; + FNodeDataSize := -1; + Width := 200; + Height := 100; + TabStop := True; + ParentColor := False; + FDefaultNodeHeight := cInitialDefaultNodeHeight; + FDragOperations := [doCopy, doMove]; + FHotCursor := crDefault; + FScrollBarOptions := TScrollBarOptions.Create(Self); + FFocusedColumn := NoColumn; + FDragImageKind := diComplete; + FLastSelectionLevel := -1; + FSelectionBlendFactor := 128; + + FIndent := 18; + + FPlusBM := TBitmap.Create; + FHotPlusBM := TBitmap.Create; + FMinusBM := TBitmap.Create; + FHotMinusBM := TBitmap.Create; + FSelectedHotPlusBM := TBitmap.Create; + FSelectedHotMinusBM := TBitmap.Create; + + FBorderStyle := TFormBorderStyle.bsSingle; + FButtonStyle := bsRectangle; + FButtonFillMode := fmTreeColor; + + FHeader := GetHeaderClass.Create(Self); + + // we have an own double buffer handling + inherited DoubleBuffered := False; + + FCheckImageKind := ckSystemDefault; + + FImageChangeLink := TChangeLink.Create; + FImageChangeLink.OnChange := ImageListChange; + FStateChangeLink := TChangeLink.Create; + FStateChangeLink.OnChange := ImageListChange; + FCustomCheckChangeLink := TChangeLink.Create; + FCustomCheckChangeLink.OnChange := ImageListChange; + + FAutoExpandDelay := 1000; + FAutoScrollDelay := 1000; + FAutoScrollInterval := 1; + + FBackground := TVTBackground.Create; + // Similar to the Transparent property of TImage, + // this flag is Off by default. + FBackGroundImageTransparent := False; + + FDefaultPasteMode := amAddChildLast; + FMargin := 4; + FTextMargin := cDefaultTextMargin; + FImagesMargin := 2; + FLastDragEffect := DROPEFFECT_NONE; + FDragType := dtOLE; + FDragHeight := 350; + FDragWidth := 200; + + FColors := TVTColors.Create(Self); + FEditDelay := 1000; + + FAnimationDuration := 200; + FSearchTimeout := 1000; + FSearchStart := ssFocusedNode; + FNodeAlignment := naProportional; + FLineStyle := lsDotted; + FIncrementalSearch := isNone; + FClipboardFormats := TClipboardFormats.Create(Self); + FOptions := GetOptionsClass.Create(Self); + + Touch.InteractiveGestures := [igPan, igPressAndTap]; + Touch.InteractiveGestureOptions := [igoPanInertia, + igoPanSingleFingerHorizontal, igoPanSingleFingerVertical, + igoPanGutter, igoParentPassthrough]; + + if not (csDesigning in ComponentState) then //Don't create worker thread in IDE, there is no use for it + TWorkerThread.AddThreadReference(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +destructor TBaseVirtualTree.Destroy(); +var + WasValidating: Boolean; +begin + WasValidating := (tsValidating in FStates) or (tsValidationNeeded in FStates); // Checking tsValidating is not enough, the TWorkerThread may be stuck in the first call to ChangeTreeStatesAsync() + InterruptValidation(True); + if WasValidating then + begin + // Make sure we dequeue the two synchronized calls from ChangeTreeStatesAsync(), fixes mem leak and AV reported in issue #1001, but is more a workaround. + while CheckSynchronize() and (FPendingSyncProcs>0) do + Sleep(1); + end;// if + FOptions.InternalSetMiscOptions(FOptions.MiscOptions - [toReadOnly]); //SetMiscOptions has side effects + // Make sure there is no reference remaining to the releasing tree. + TWorkerThread.ReleaseThreadReference(IsLibrary); // see issue #1245 + StopWheelPanning; + CancelEditNode; + + // Just in case it didn't happen already release the edit link. + FEditLink := nil; + FClipboardFormats.Free; + // Clear will also free the drag manager if it is still alive. + Clear; + FColors.Free; + FBackground.Free; + + if CheckImageKind = ckSystemDefault then + FCheckImages.Free; + FScrollBarOptions.Free; + + // The window handle must be destroyed before the header is freed because it is needed in WM_NCDESTROY. + if HandleAllocated then + DestroyWindowHandle; + + // Release FDottedBrush in case WM_NCDESTROY hasn't been triggered. + if Assigned(DottedBrushTreeLines) then + begin + DottedBrushTreeLines.Bitmap.Free(); + DottedBrushTreeLines.Free; + DottedBrushTreeLines:= nil; + end; + + FHeader.Free; + FHeader := nil; // Do not use FreeAndNil() before checking issue #497 + FreeAndNil(FOptions); // WM_NCDESTROY accesses FOptions + + FreeMem(FRoot); + + FPlusBM.Free; + FHotPlusBM.Free; + FMinusBM.Free; + FHotMinusBM.Free; + FSelectedHotPlusBM.Free; + FSelectedHotMinusBM.Free; + + // Fixes issue #1002 + Images := nil; + StateImages := nil; + CustomCheckImages := nil; + + FImageChangeLink.Free; + FStateChangeLink.Free; + FCustomCheckChangeLink.Free; + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.AdjustTotalCount(Node: PVirtualNode; Value: Integer; Relative: Boolean = False); + +// Sets a node's total count to the given value and recursively adjusts the parent's total count +// (actually, the adjustment is done iteratively to avoid function call overheads). + +var + Difference: Integer; + Run: PVirtualNode; + +begin + if Relative then + Difference := Value + else + Difference := Value - Integer(Node.TotalCount); + if Difference <> 0 then + begin + Run := Node; + // Root node has as parent the tree view. + while Assigned(Run) and (Run <> Pointer(Self)) do + begin + System.Inc(Integer(Run.TotalCount), Difference); + Run := Run.Parent; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.AdjustTotalHeight(Node: PVirtualNode; Value: TNodeHeight; Relative: Boolean = False); + +// Sets a node's total height to the given value and recursively adjusts the parent's total height. + +var + Difference: TNodeHeight; + Run: PVirtualNode; + +begin + if Relative then + Difference := Value + else + Difference := Value - Node.TotalHeight; + if Difference <> 0 then + begin + Run := Node; + repeat + Inc(Run.TotalHeight, Difference); + + // If the node is not visible or the parent node is not expanded or we are already at the top + // then nothing more remains to do. + if not (vsVisible in Run.States) or (Run = FRoot) or + (Run.Parent = nil) or not (vsExpanded in Run.Parent.States) then + Break; + + Run := Run.Parent; + until False; + end; + + UpdateVerticalRange; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CalculateCacheEntryCount: Integer; + +// Calculates the size of the position cache. + +begin + if FVisibleCount > 1 then + Result := Ceil(FVisibleCount / CacheThreshold) + else + Result := 0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CalculateVerticalAlignments(var PaintInfo: TVTPaintInfo; var VButtonAlign: TDimension); + +// Calculates the vertical alignment of the given node and its associated expand/collapse button during +// a node paint cycle depending on the required node alignment style. + +begin + With PaintInfo do begin + // For absolute alignment the calculation is trivial. + case FNodeAlignment of + naFromTop: + VAlign := Node.Align; + naFromBottom: + VAlign := NodeHeight[Node] - Node.Align; + else // naProportional + // Consider button and line alignment, but make sure neither the image nor the button (whichever is taller) + // go out of the entire node height (100% means bottom alignment to the node's bounds). + if (ImageInfo[iiNormal].Index >= 0) or (ImageInfo[iiState].Index >= 0) then + begin + if (ImageInfo[iiNormal].Index >= 0) then + VAlign := ImageInfo[iiNormal].Images.Height + else + VAlign := ImageInfo[iiState].Images.Height; + VAlign := MulDiv((NodeHeight[Node] - VAlign), Node.Align, 100) + Divide(VAlign, 2); + end + else + if toShowButtons in FOptions.PaintOptions then + VAlign := MulDiv((NodeHeight[Node] - FPlusBM.Height), Node.Align, 100) + Divide(FPlusBM.Height, 2) + else + VAlign := MulDiv(Node.NodeHeight, Node.Align, 100); + end; + + VButtonAlign := VAlign - FPlusBM.Height div 2 - (FPlusBM.Height and 1); + end;// With PaintInfo +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ChangeCheckState(Node: PVirtualNode; Value: TCheckState): Boolean; + +// Sets the check state of the node according to the given value and the node's check type. +// If the check state must be propagated to the parent nodes and one of them refuses to change then +// nothing happens and False is returned, otherwise True. + +var + Run: PVirtualNode; + UncheckedCount, + MixedCheckCount, + CheckedCount: Cardinal; + +begin + Result := not (vsChecking in Node.States); + with Node^ do + if Result then + begin + Include(States, vsChecking); + try + if not (vsInitialized in States) then + InitNode(Node) + else if CheckState = Value then + begin + // Value didn't change and node was initialized, so nothing to do + Result := False; + Exit; + end;//if + + // Indicate that we are going to propagate check states up and down the hierarchy. + if FCheckPropagationCount = 0 then begin + // Do not enter tsCheckPropagation more than once + DoStateChange([tsCheckPropagation]); + BeginUpdate(); + end; + System.Inc(FCheckPropagationCount); + try + // Do actions which are associated with the given check state. + case CheckType of + // Check state change with additional consequences for check states of the children. + ctTriStateCheckBox: + begin + // Propagate state down to the children. + if toAutoTristateTracking in FOptions.AutoOptions then + case Value of + csUncheckedNormal: + if Node.ChildCount > 0 then + begin + Run := FirstChild; + CheckedCount := 0; + MixedCheckCount := 0; + UncheckedCount := 0; + while Assigned(Run) do + begin + if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then + begin + if not Self.GetCheckState(Run).IsDisabled() then + SetCheckState(Run, csUncheckedNormal); + // Check if the new child state was set successfully, otherwise we have to adjust the + // node's new check state accordingly. + case Self.GetCheckState(Run) of + csCheckedNormal, csCheckedDisabled: + System.Inc(CheckedCount); + csMixedNormal: + System.Inc(MixedCheckCount); + csUncheckedNormal, csUncheckedDisabled: + System.Inc(UncheckedCount); + end; + end; + Run := Run.NextSibling; + end; + + // If there is still a mixed state child node checkbox then this node must be mixed checked too. + if MixedCheckCount > 0 then + Value := csMixedNormal + else + // If nodes are normally checked child nodes then the unchecked count determines what + // to set for the node itself. + if CheckedCount > 0 then + if UncheckedCount > 0 then + Value := csMixedNormal + else + Value := csCheckedNormal; + end; + csCheckedNormal: + if Node.ChildCount > 0 then + begin + Run := FirstChild; + CheckedCount := 0; + MixedCheckCount := 0; + UncheckedCount := 0; + while Assigned(Run) do + begin + if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then + begin + if not Self.GetCheckState(Run).IsDisabled() then + SetCheckState(Run, csCheckedNormal); + // Check if the new child state was set successfully, otherwise we have to adjust the + // node's new check state accordingly. + case Self.GetCheckState(Run) of + csCheckedNormal: + System.Inc(CheckedCount); + csMixedNormal: + System.Inc(MixedCheckCount); + csUncheckedNormal: + System.Inc(UncheckedCount); + end; + end; + Run := Run.NextSibling; + end; + + // If there is still a mixed state child node checkbox then this node must be mixed checked too. + if MixedCheckCount > 0 then + Value := csMixedNormal + else + // If nodes are normally checked child nodes then the unchecked count determines what + // to set for the node itself. + if CheckedCount > 0 then + if UncheckedCount > 0 then + Value := csMixedNormal + else + Value := csCheckedNormal; + end; + end; + end; + // radio button check state change + ctRadioButton: + if Value = csCheckedNormal then + begin + Value := csCheckedNormal; + // Make sure only this node is checked. + Run := Parent.FirstChild; + while Assigned(Run) do + begin + if Run.CheckType = ctRadioButton then + Run.CheckState := csUncheckedNormal; + Run := Run.NextSibling; + end; + Invalidate; + end; + end; + + if Result then + CheckState := Value // Set new check state + else + CheckState := Self.GetCheckState(Node).GetUnpressed(); // Reset dynamic check state. + + // Propagate state up to the parent. + if not (vsInitialized in Parent.States) then + InitNode(Parent); + if (toAutoTristateTracking in FOptions.AutoOptions) and ([vsChecking, vsDisabled] * Parent.States = []) and + (CheckType in [ctCheckBox, ctTriStateCheckBox]) and (Parent <> FRoot) and + (Parent.CheckType = ctTriStateCheckBox) then + Result := CheckParentCheckState(Node, Value) + else + Result := True; + + InvalidateNode(Node); + finally + System.Dec(FCheckPropagationCount); // WL, 05.02.2004 + if FCheckPropagationCount = 0 then begin + // Allow state change event after all check operations finished + DoStateChange([], [tsCheckPropagation]); + EndUpdate(); + end; + end; + finally + Exclude(States, vsChecking); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CollectSelectedNodesLTR(MainColumn: Integer; NodeLeft, NodeRight: TDimension; Alignment: TAlignment; + OldRect, NewRect: TRect): Boolean; + +// Helper routine used when a draw selection takes place. This version handles left-to-right directionality. +// In the process of adding or removing nodes the current selection is modified which requires to pack it after +// the function returns. Another side effect of this method is that a temporary list of nodes will be created +// (see also InternalCacheNode) which must be inserted into the current selection by the caller. + +var + Run, + NextNode: PVirtualNode; + TextRight, + TextLeft, + CurrentTop, + CurrentRight, + NextTop: TDimension; + NextColumn, + Dummy: Integer; + DummyLeft: TDimension; + + MinY, MaxY: TDimension; + LabelOffset: TDimension; + IsInOldRect, + IsInNewRect: Boolean; + NodeWidth: TDimension; + + // quick check variables for various parameters + DoSwitch, + AutoSpan: Boolean; + SimpleSelection: Boolean; + +begin + // A priori nothing changes. + Result := False; + + // Determine minimum and maximum vertical coordinates to limit iteration to. + MinY := Min(OldRect.Top, NewRect.Top); + MaxY := Max(OldRect.Bottom, NewRect.Bottom); + + // Initialize short hand variables to speed up tests below. + DoSwitch := ssCtrl in FDrawSelShiftState; + AutoSpan := FHeader.UseColumns and (toAutoSpanColumns in FOptions.AutoOptions); + SimpleSelection := toSimpleDrawSelection in FOptions.SelectionOptions; + // This is the node to start with. + Run := GetNodeAt(0, MinY, False, CurrentTop); + + if Assigned(Run) then + begin + LabelOffset := GetOffset(TVTElement.ofsLabel, Run); + + // ----- main loop + // Change selection depending on the node's rectangle being in the selection rectangle or not, but + // touch only those nodes which overlap either the old selection rectangle or the new one but not both. + repeat + // Collect offsets for check, normal and state images. + TextLeft := NodeLeft + LabelOffset; + NextTop := CurrentTop + NodeHeight[Run]; + + // Simple selection allows to draw the selection rectangle anywhere. No intersection with node captions is + // required. Only top and bottom bounds of the rectangle matter. + if SimpleSelection or (toFullRowSelect in FOptions.SelectionOptions) then + begin + IsInOldRect := (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom) and + ((FHeader.Columns.Count = 0) or (FHeader.Columns.TotalWidth > OldRect.Left)) and ((NodeLeft + LabelOffset) < OldRect.Right); + IsInNewRect := (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom) and + ((FHeader.Columns.Count = 0) or (FHeader.Columns.TotalWidth > NewRect.Left)) and ((NodeLeft + LabelOffset) < NewRect.Right); + end + else + begin + // The right column border might be extended if column spanning is enabled. + if AutoSpan then + begin + with FHeader.Columns do + begin + NextColumn := MainColumn; + repeat + Dummy := GetNextVisibleColumn(NextColumn); + if (Dummy = InvalidColumn) or not ColumnIsEmpty(Run, Dummy) or + (Items[Dummy].BidiMode <> bdLeftToRight) then + Break; + NextColumn := Dummy; + until False; + if NextColumn = MainColumn then + CurrentRight := NodeRight + else + GetColumnBounds(NextColumn, DummyLeft, CurrentRight); + end; + end + else + CurrentRight := NodeRight; + // Check if we need the node's width. This is the case when the node is not left aligned or the + // left border of the selection rectangle is to the right of the left node border. + if (TextLeft < OldRect.Left) or (TextLeft < NewRect.Left) or (Alignment <> taLeftJustify) then + begin + NodeWidth := DoGetNodeWidth(Run, MainColumn); + if NodeWidth >= (CurrentRight - TextLeft) then + TextRight := CurrentRight + else + case Alignment of + taLeftJustify: + TextRight := TextLeft + NodeWidth; + taCenter: + begin + TextLeft := Divide(TextLeft + CurrentRight - NodeWidth, 2); + TextRight := TextLeft + NodeWidth; + end; + else + // taRightJustify + TextRight := CurrentRight; + TextLeft := TextRight - NodeWidth; + end; + end + else + TextRight := CurrentRight; + + // Now determine whether we need to change the state. + IsInOldRect := (OldRect.Left <= TextRight) and (OldRect.Right >= TextLeft) and + (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom); + IsInNewRect := (NewRect.Left <= TextRight) and (NewRect.Right >= TextLeft) and + (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom); + end; + + if IsInOldRect xor IsInNewRect then + begin + Result := True; + if DoSwitch then + begin + if vsSelected in Run.States then + InternalRemoveFromSelection(Run) + else + InternalCacheNode(Run); + end + else + begin + if IsInNewRect then + InternalCacheNode(Run) + else + InternalRemoveFromSelection(Run); + end; + end; + CurrentTop := NextTop; + // Get next visible node and update left node position. + NextNode := GetNextVisibleNoInit(Run, True); + if NextNode = nil then + Break; + Inc(NodeLeft, CountLevelDifference(Run, NextNode) * FIndent); + Run := NextNode; + until CurrentTop > MaxY; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CollectSelectedNodesRTL(MainColumn: Integer; NodeLeft, NodeRight: TDimension; Alignment: TAlignment; + OldRect, NewRect: TRect): Boolean; + +// Helper routine used when a draw selection takes place. This version handles right-to-left directionality. +// See also comments in CollectSelectedNodesLTR. + +var + Run, + NextNode: PVirtualNode; + NextColumn, + Dummy: Integer; + + DummyRight, + TextRight, + TextLeft, + CheckOffset, + CurrentTop, + CurrentLeft, + NextTop, + NodeWidth, + MinY, MaxY: TDimension; + IsInOldRect, + IsInNewRect: Boolean; + + // quick check variables for various parameters + WithCheck, + WithStateImages, + DoSwitch, + AutoSpan: Boolean; + SimpleSelection: Boolean; + +begin + // A priori nothing changes. + Result := False; + // Switch the alignment to the opposite value in RTL context. + ChangeBiDiModeAlignment(Alignment); + + // Determine minimum and maximum vertical coordinates to limit iteration to. + MinY := Min(OldRect.Top, NewRect.Top); + MaxY := Max(OldRect.Bottom, NewRect.Bottom); + + // Initialize short hand variables to speed up tests below. + DoSwitch := ssCtrl in FDrawSelShiftState; + WithCheck := (toCheckSupport in FOptions.MiscOptions) and Assigned(FCheckImages); + // Don't check the events here as descendant trees might have overriden the DoGetImageIndex method. + WithStateImages := Assigned(FStateImages) or Assigned(OnGetImageIndexEx); + if WithCheck then + CheckOffset := FCheckImages.Width + FImagesMargin + else + CheckOffset := 0; + AutoSpan := FHeader.UseColumns and (toAutoSpanColumns in FOptions.AutoOptions); + SimpleSelection := toSimpleDrawSelection in FOptions.SelectionOptions; + // This is the node to start with. + Run := GetNodeAt(0, MinY, False, CurrentTop); + + if Assigned(Run) then + begin + // The initial minimal left border is determined by the identation level of the node and is dynamically adjusted. + if toShowRoot in FOptions.PaintOptions then + Dec(NodeRight, (TDimension((GetNodeLevel(Run) + 1)) * FIndent) + FMargin) + else + Dec(NodeRight, (TDimension(GetNodeLevel(Run)) * FIndent) + FMargin); + + // ----- main loop + // Change selection depending on the node's rectangle being in the selection rectangle or not, but + // touch only those nodes which overlap either the old selection rectangle or the new one but not both. + repeat + // Collect offsets for check, normal and state images. + TextRight := NodeRight; + if WithCheck and (Run.CheckType <> ctNone) then + Dec(TextRight, CheckOffset); + Dec(TextRight, GetImageSize(Run, ikNormal, MainColumn).cx); + if WithStateImages then + Dec(TextRight, GetImageSize(Run, ikState, MainColumn).cx); + NextTop := CurrentTop + NodeHeight[Run]; + + // Simple selection allows to draw the selection rectangle anywhere. No intersection with node captions is + // required. Only top and bottom bounds of the rectangle matter. + if SimpleSelection then + begin + IsInOldRect := (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom); + IsInNewRect := (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom); + end + else + begin // The left column border might be extended if column spanning is enabled. + if AutoSpan then + begin + NextColumn := MainColumn; + repeat + Dummy := FHeader.Columns.GetPreviousVisibleColumn(NextColumn); + if (Dummy = InvalidColumn) or not ColumnIsEmpty(Run, Dummy) or + (FHeader.Columns[Dummy].BiDiMode = bdLeftToRight) then + Break; + NextColumn := Dummy; + until False; + if NextColumn = MainColumn then + CurrentLeft := NodeLeft + else + FHeader.Columns.GetColumnBounds(NextColumn, CurrentLeft, DummyRight); + end + else + CurrentLeft := NodeLeft; + // Check if we need the node's width. This is the case when the node is not left aligned (in RTL context this // means actually right aligned) or the right border of the selection rectangle is to the left + // of the right node border. + if (TextRight > OldRect.Right) or (TextRight > NewRect.Right) or (Alignment <> taRightJustify) then + begin + NodeWidth := DoGetNodeWidth(Run, MainColumn); + if NodeWidth >= (TextRight - CurrentLeft) then + TextLeft := CurrentLeft + else + case Alignment of + taLeftJustify: + begin + TextLeft := CurrentLeft; + TextRight := TextLeft + NodeWidth; + end; + taCenter: + begin + TextLeft := Divide(TextRight + CurrentLeft - NodeWidth, 2); + TextRight := TextLeft + NodeWidth; + end; + else + // taRightJustify + TextLeft := TextRight - NodeWidth; + end; + end + else + TextLeft := CurrentLeft; + + // Now determine whether we need to change the state. + IsInOldRect := (OldRect.Right >= TextLeft) and (OldRect.Left <= TextRight) and + (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom); + IsInNewRect := (NewRect.Right >= TextLeft) and (NewRect.Left <= TextRight) and + (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom); + end; + + if IsInOldRect xor IsInNewRect then + begin + Result := True; + if DoSwitch then + begin + if vsSelected in Run.States then + InternalRemoveFromSelection(Run) + else + InternalCacheNode(Run); + end + else + begin + if IsInNewRect then + InternalCacheNode(Run) + else + InternalRemoveFromSelection(Run); + end; + end; + CurrentTop := NextTop; + // Get next visible node and update left node position. + NextNode := GetNextVisibleNoInit(Run, True); + if NextNode = nil then + Break; + Dec(NodeRight, CountLevelDifference(Run, NextNode) * FIndent); + Run := NextNode; + until CurrentTop > MaxY; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ClearNodeBackground(const PaintInfo: TVTPaintInfo; UseBackground, Floating: Boolean; + R: TRect); + +// Erases a node's background depending on what the application decides to do. +// UseBackground determines whether or not to use the background picture, while Floating indicates +// that R is given in coordinates of the small node bitmap or the superordinated target bitmap used in PaintTree. + +var + BackColor: TColor; + EraseAction: TItemEraseAction; + Offset: TPoint; + +begin + BackColor := FColors.BackGroundColor; + with PaintInfo do + begin + EraseAction := eaDefault; + + if Floating then + begin + Offset := Point(-FEffectiveOffsetX, R.Top); + OffsetRect(R, 0, -Offset.Y); + end + else + Offset := Point(0, 0); + + DoBeforeItemErase(Canvas, Node, R, BackColor, EraseAction); + + with Canvas do + begin + case EraseAction of + eaNone: + ; + eaColor: + begin + // User has given a new background color. + Brush.Color := BackColor; + FillRect(R); + end; + else // eaDefault + if UseBackground then + begin + if toStaticBackground in TreeOptions.PaintOptions then + StaticBackground(FBackground, Canvas, Offset, R, FColors.BackGroundColor) + else + TileBackground(FBackground, Canvas, Offset, R, FColors.BackGroundColor); + end + else + begin + if (poDrawSelection in PaintOptions) and (toFullRowSelect in FOptions.SelectionOptions) and + (vsSelected in Node.States) and not (toUseBlendedSelection in FOptions.PaintOptions) and not + (tsUseExplorerTheme in FStates) then + begin + if toShowHorzGridLines in FOptions.PaintOptions then + begin + Brush.Color := BackColor; + FillRect(Rect(R.Left, R.Bottom - 1, R.Right, R.Bottom)); + Dec(R.Bottom); + end; + if Focused or (toPopupMode in FOptions.PaintOptions) then + begin + Brush.Color := FColors.FocusedSelectionColor; + Pen.Color := FColors.FocusedSelectionBorderColor; + end + else + begin + Brush.Color := FColors.UnfocusedSelectionColor; + Pen.Color := FColors.UnfocusedSelectionBorderColor; + end; + + RoundRect(R.Left, R.Top, R.Right, R.Bottom, FSelectionCurveRadius, FSelectionCurveRadius); + end + else + begin + Brush.Color := BackColor; + FillRect(R); + end; + end; + end; + DoAfterItemErase(Canvas, Node, R); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CompareNodePositions(Node1, Node2: PVirtualNode; ConsiderChildrenAbove: Boolean = False): Integer; + +// Tries hard and smart to quickly determine whether Node1's structural position is before Node2's position. +// If ConsiderChildrenAbove is True, the nodes will be compared with their visual order in mind. +// Returns 0 if Node1 = Node2, < 0 if Node1 is located before Node2 else > 0. + +var + Run1, + Run2: PVirtualNode; + Level1, + Level2: Cardinal; + +begin + Assert(Assigned(Node1) and Assigned(Node2), 'Nodes must never be nil.'); + + if Node1 = Node2 then + Result := 0 + else + begin + if HasAsParent(Node1, Node2) then + Result := IfThen(ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions), -1, 1) + else + if HasAsParent(Node2, Node1) then + Result := IfThen(ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions), 1, -1) + else + begin + // the given nodes are neither equal nor are they parents of each other, so go up to FRoot + // for each node and compare the child indices of the top level parents + // Note: neither Node1 nor Node2 can be FRoot at this point as this (a bit strange) circumstance would + // be caught by the previous code. + + // start lookup at the same level + Level1 := GetNodeLevel(Node1); + Level2 := GetNodeLevel(Node2); + Run1 := Node1; + while Level1 > Level2 do + begin + Run1 := Run1.Parent; + System.Dec(Level1); + end; + Run2 := Node2; + while Level2 > Level1 do + begin + Run2 := Run2.Parent; + System.Dec(Level2); + end; + + // now go up until we find a common parent node (loop will safely stop at FRoot if the nodes + // don't share a common parent) + while Run1.Parent <> Run2.Parent do + begin + Run1 := Run1.Parent; + Run2 := Run2.Parent; + end; + Result := Integer(Run1.Index) - Integer(Run2.Index); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DrawLineImage(const PaintInfo: TVTPaintInfo; X, Y, H, VAlign: TDimension; Style: TVTLineType; + Reverse: Boolean); + +// Draws (depending on Style) one of the 5 line types of the tree. +// If Reverse is True then a right-to-left column is being drawn, hence horizontal lines must be mirrored. +// X and Y describe the left upper corner of the line image rectangle, while H denotes its height (and width). + +var + HalfWidth, + TargetX: TDimension; + +begin + HalfWidth := Divide(FIndent, 2); + if Reverse then + TargetX := 0 + else + TargetX := FIndent - 1; + + with PaintInfo.Canvas do + begin + case Style of + ltBottomRight: + begin + DrawDottedVLine(PaintInfo, Y + VAlign, Y + H, X + HalfWidth); + DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign); + end; + ltTopDown: + DrawDottedVLine(PaintInfo, Y, Y + H, X + HalfWidth); + ltTopDownRight: + begin + DrawDottedVLine(PaintInfo, Y, Y + H, X + HalfWidth); + DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign); + end; + ltRight: + DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign); + ltTopRight: + begin + DrawDottedVLine(PaintInfo, Y, Y + VAlign, X + HalfWidth); + DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign); + end; + ltLeft: // left can also mean right for RTL context + if Reverse then + DrawDottedVLine(PaintInfo, Y, Y + H, X + FIndent) + else + DrawDottedVLine(PaintInfo, Y, Y + H, X); + ltLeftBottom: + if Reverse then + begin + DrawDottedVLine(PaintInfo, Y, Y + H, X + FIndent); + DrawDottedHLine(PaintInfo, X, X + FIndent, Y + H); + end + else + begin + DrawDottedVLine(PaintInfo, Y, Y + H, X); + DrawDottedHLine(PaintInfo, X, X + FIndent, Y + H); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.FindInPositionCache(Node: PVirtualNode; var CurrentPos: TNodeHeight): PVirtualNode; + +// Looks through the position cache and returns the node whose top position is the largest one which is smaller or equal +// to the position of the given node. + +var + L, H, I: Integer; + +begin + L := 0; + H := High(FPositionCache); + while L <= H do + begin + I := (L + H) shr 1; + if CompareNodePositions(FPositionCache[I].Node, Node) <= 0 then + L := I + 1 + else + H := I - 1; + end; + if L = 0 then // High(FPositionCache) = -1 + begin + Result := nil; + CurrentPos := 0; + end + else + begin + Result := FPositionCache[L - 1].Node; + CurrentPos := FPositionCache[L - 1].AbsoluteTop; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.FindInPositionCache(Position: TDimension; var CurrentPos: TNodeHeight): PVirtualNode; + +// Looks through the position cache and returns the node whose top position is the largest one which is smaller or equal +// to the given vertical position. +// The returned node does not necessarily occupy the given position but is the nearest one to start +// iterating from to approach the real node for a given position. CurrentPos receives the actual position of the found +// node which is needed for further iteration. + +var + L, H, I: Integer; + +begin + L := 0; + H := High(FPositionCache); + while L <= H do + begin + I := (L + H) shr 1; + if FPositionCache[I].AbsoluteTop <= Position then + L := I + 1 + else + H := I - 1; + end; + if L = 0 then // High(FPositionCache) = -1 + begin + Result := nil; + CurrentPos := 0; + end + else + begin + Result := FPositionCache[L - 1].Node; + CurrentPos := FPositionCache[L - 1].AbsoluteTop; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.FixupTotalCount(Node: PVirtualNode); + +// Called after loading a subtree from stream. The child count in each node is already set but not +// their total count. + +var + Child: PVirtualNode; + +begin + // Initial total count is set to one on node creation. + Child := Node.FirstChild; + while Assigned(Child) do + begin + FixupTotalCount(Child); + System.Inc(Node.TotalCount, Child.TotalCount); + Child := Child.NextSibling; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.FixupTotalHeight(Node: PVirtualNode); + +// Called after loading a subtree from stream. The individual height of each node is set already, +// but their total height needs an adjustment depending on their visibility state. + +var + Child: PVirtualNode; + +begin + // Initial total height is set to the node height on load. + Child := Node.FirstChild; + + if vsExpanded in Node.States then + begin + while Assigned(Child) do + begin + FixupTotalHeight(Child); + if vsVisible in Child.States then + Inc(Node.TotalHeight, Child.TotalHeight); + Child := Child.NextSibling; + end; + end + else + begin + // The node is collapsed, so just update the total height of its child nodes. + while Assigned(Child) do + begin + FixupTotalHeight(Child); + Child := Child.NextSibling; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetBottomNode: PVirtualNode; + +begin + Result := GetNodeAt(0, ClientHeight - 1); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetCheckedCount: Integer; + +var + Node: PVirtualNode; + +begin + Result := 0; + Node := GetFirstChecked; + while Assigned(Node) do + begin + System.Inc(Result); + Node := GetNextChecked(Node); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetCheckState(Node: PVirtualNode): TCheckState; + +begin + if Assigned(FOnBeforeGetCheckState) then + FOnBeforeGetCheckState(Self, Node); + + Result := Node.CheckState; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetCheckType(Node: PVirtualNode): TCheckType; + +begin + Result := Node.CheckType; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetChildCount(Node: PVirtualNode): Cardinal; +begin + if (Node = nil) or (Node = FRoot) then + Exit(FRoot.ChildCount); + if not GetChildrenInitialized(Node) then + InitChildren(Node); + Exit(Node.ChildCount); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetChildrenInitialized(Node: PVirtualNode): Boolean; + +begin + Result := not (vsHasChildren in Node.States) or (Node.ChildCount > 0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetCutCopyCount: Integer; + +var + Node: PVirtualNode; + +begin + Result := 0; + Node := GetFirstCutCopy; + while Assigned(Node) do + begin + System.Inc(Result); + Node := GetNextCutCopy(Node); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetDisabled(Node: PVirtualNode): Boolean; + +begin + Result := Assigned(Node) and (vsDisabled in Node.States); +end; + +//---------------------------------------------------------------------------------------------------------------------- +// whether the sync of checkbox with selection is allowed for this node +function TBaseVirtualTree.GetSyncCheckstateWithSelection(Node: PVirtualNode): Boolean; + +begin + Result := (toSyncCheckboxesWithSelection in FOptions.SelectionOptions) + and (toCheckSupport in FOptions.MiscOptions) + and Assigned(FCheckImages) + and (Node.CheckType = ctCheckBox); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetDragManager: IVTDragManager; + +// Returns the internal drag manager interface. If this does not yet exist then it is created here. + +begin + if FDragManager = nil then + begin + FDragManager := DoCreateDragManager; + if FDragManager = nil then + FDragManager := TVTDragManager.Create(Self) as IVTDragManager; + end; + + Result := FDragManager; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetExpanded(Node: PVirtualNode): Boolean; + +begin + if Assigned(Node) then + Result := vsExpanded in Node.States + else + Result := False; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFiltered(Node: PVirtualNode): Boolean; + +begin + Result := vsFiltered in Node.States; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFullyVisible(Node: PVirtualNode): Boolean; + +// Determines whether the given node has the visibility flag set as well as all its parents are expanded. + +begin + Assert(Assigned(Node), 'Invalid parameter.'); + Result := vsVisible in Node.States; + if Result and (Node <> FRoot) then + Result := VisiblePath[Node]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetHasChildren(Node: PVirtualNode): Boolean; + +begin + if Assigned(Node) then + Result := vsHasChildren in Node.States + else + Result := vsHasChildren in FRoot.States; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetMultiline(Node: PVirtualNode): Boolean; + +begin + Result := Assigned(Node) and (Node <> FRoot) and (vsMultiline in Node.States); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNodeHeight(Node: PVirtualNode): TNodeHeight; + +begin + if Assigned(Node) and (Node <> FRoot) then + begin + if (toVariableNodeHeight in FOptions.MiscOptions) and not (vsDeleting in Node.States) then + begin + if not (vsInitialized in Node.States) then + InitNode(Node); + + // Ensure the node's height is determined. + MeasureItemHeight(Self.Canvas, Node); + end; + Result := Node.NodeHeight; + end + else + Result := 0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNodeParent(Node: PVirtualNode): PVirtualNode; + +begin + if Assigned(Node) and (Node.Parent <> FRoot) then + Result := Node.Parent + else + Result := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetOffset(pElement: TVTElement; pNode: PVirtualNode): TDimension; +// Calculates the offset of the given element +var + lOffsets: TVTOffsets; +begin + GetOffsets(pNode, lOffsets, pElement); + Exit(lOffsets[pElement]); +end; + +procedure TBaseVirtualTree.GetOffsets(pNode: PVirtualNode; out pOffsets: TVTOffsets; pElement: TVTElement = TVTElement.ofsEndOfClientArea; pColumn: Integer = NoColumn); +// Calculates the offset up to the given element and supplies them in an array. +var + lNodeLevel: Integer; + lNodeIndent: TDimension; +begin + // If no specific column was given, assume the main column + if pColumn = -1 then + pColumn := Header.MainColumn; + + // Left Margin + pOffsets[TVTElement.ofsMargin] := FMargin; + if pElement = ofsMargin then + exit; + + pOffsets[TVTElement.ofsToggleButton] := pOffsets[TVTElement.ofsMargin]; + pOffsets[TVTElement.ofsCheckBox] := pOffsets[TVTElement.ofsMargin]; + if (pColumn = Header.MainColumn) then + begin + if not (toFixedIndent in TreeOptions.PaintOptions) then + begin + // plus Indent + lNodeLevel := GetNodeLevel(pNode); + if toShowRoot in FOptions.PaintOptions then + System.Inc(lNodeLevel); + end + else + lNodeLevel := 1; + lNodeIndent := lNodeLevel * TDimension(FIndent); + // toggle buttons + Inc(pOffsets[TVTElement.ofsToggleButton], lNodeIndent); + Dec(pOffsets[TVTElement.ofsToggleButton], Divide((TDimension(FIndent) - FPlusBM.Width), 2) - 1 + FPlusBM.Width); //Compare PaintTree() relative line 107 + // checkbox + Inc(pOffsets[TVTElement.ofsCheckBox], lNodeIndent); + end;//if MainColumn + + // The area in which the toggle buttons are painted must have exactly the size of one indent level + if pElement <= TVTElement.ofsToggleButton then + exit; + + if (toCheckSupport in TreeOptions.MiscOptions) and Assigned(FCheckImages) and (pNode.CheckType <> ctNone) and (pColumn = Header.MainColumn) then + begin + Inc(pOffsets[TVTElement.ofsCheckBox], fImagesMargin); + + // right of checkbox, left of state image + pOffsets[TVTElement.ofsStateImage] := pOffsets[TVTElement.ofsCheckBox] + FCheckImages.Width + fImagesMargin; + end else + pOffsets[TVTElement.ofsStateImage] := pOffsets[TVTElement.ofsCheckBox]; + if pElement <= TVTElement.ofsStateImage then + exit; + + // right of left image, left of normal image + pOffsets[TVTElement.ofsImage] := pOffsets[TVTElement.ofsStateImage] + GetImageSize(pNode, TVTImageKind.ikState, pColumn).cx; + if pElement = TVTElement.ofsImage then + exit; + + // label + pOffsets[TVTElement.ofsLabel] := pOffsets[TVTElement.ofsImage] + GetImageSize(pNode, TVTImageKind.ikNormal, pColumn).cx; + pOffsets[TVTElement.ofsText] := pOffsets[TVTElement.ofsLabel] + FTextMargin; + Dec(pOffsets[TVTElement.ofsText]); //TODO: This should no longer be necessary once issue #369 is resolved. + if pElement <= TVTElement.ofsText then + exit; + + // End of text + pOffsets[TVTElement.ofsRightOfText] := pOffsets[TVTElement.ofsText] + DoGetNodeWidth(pNode, pColumn) + DoGetNodeExtraWidth(pNode, pColumn); + + // end of client area + pOffsets[TVTElement.ofsEndOfClientArea] := Max(FRangeX, ClientWidth) - FTextMargin; +end; + +function TBaseVirtualTree.GetOffsetXY: TPoint; + +begin + Result := Point(FOffsetX, FOffsetY); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetRangeX: TDimension; +begin + Result := Max(0, FRangeX); +end; + +function TBaseVirtualTree.GetRootNodeCount: Cardinal; + +begin + Result := FRoot.ChildCount; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetSelected(Node: PVirtualNode): Boolean; + +begin + Result := Assigned(Node) and (vsSelected in Node.States); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetSelectedCount: Integer; +begin + Exit(FSelectionCount); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetSelectedData: TArray; +var + lItem: PVirtualNode; + i: Integer; +begin + SetLEngth(Result, Self.SelectedCount); + i := 0; + lItem := Self.GetFirstSelected; + while Assigned(lItem) do + begin + Result[i] := Self.GetNodeData(lItem); + lItem := Self.GetNextSelected(lItem); + System.Inc(i); + end; + SetLength(Result, i); // See issue #927, SelectedCount may not yet be updated. +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetTopNode: PVirtualNode; + +var + Dummy: TDimension; + +begin + Result := GetNodeAt(0, 0, True, Dummy); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetTotalCount(): Cardinal; + +begin + Assert(GetCurrentThreadId = MainThreadId, 'UI controls may only be used in UI thread.'); // FUpdateCount is not thread-safe! So do not write it in non-UI threads. + System.Inc(FUpdateCount); + try + ValidateNode(FRoot, True); + finally + System.Dec(FUpdateCount); + end; + // The root node itself doesn't count as node. + Result := FRoot.TotalCount - 1; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetVclStyleEnabled: Boolean; +begin + Exit(FVclStyleEnabled); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetVerticalAlignment(Node: PVirtualNode): Byte; + +begin + Result := Node.Align; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetVisible(Node: PVirtualNode): Boolean; + +// Determines if the given node is marked as being visible. + +begin + if Node = nil then + Node := FRoot; + + if not (vsInitialized in Node.States) then + InitNode(Node); + + Result := vsVisible in Node.States; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetVisiblePath(Node: PVirtualNode): Boolean; + +// Determines if all parents of the given node are expanded and have the visibility flag set. + +begin + Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameters.'); + + // FRoot is always expanded + repeat + Node := Node.Parent; + until (Node = FRoot) or not (vsExpanded in Node.States) or not (vsVisible in Node.States); + + Result := Node = FRoot; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.HandleClickSelection(LastFocused, NewNode: PVirtualNode; Shift: TShiftState; + DragPending: Boolean); + +// Handles multi-selection with mouse click. + +begin + // Ctrl key down + if ssCtrl in Shift then + begin + if ssShift in Shift then + begin + SelectNodes(FRangeAnchor, NewNode, True); + end + else + begin + if not (toSiblingSelectConstraint in FOptions.SelectionOptions) then + FRangeAnchor := NewNode; + // Delay selection change if a drag operation is pending. + // Otherwise switch selection state here. + if DragPending then + DoStateChange([tsToggleFocusedSelection]) + else + if vsSelected in NewNode.States then + RemoveFromSelection(NewNode) + else + AddToSelection(NewNode, True); + end; + end + else + // Shift key down + if ssShift in Shift then + begin + if FRangeAnchor = nil then + FRangeAnchor := FRoot.FirstChild; + + // select node range + if Assigned(FRangeAnchor) then + begin + SelectNodes(FRangeAnchor, NewNode, False); + Invalidate; + end; + end + else + begin + // any other case + if not (vsSelected in NewNode.States) then + AddToSelection(NewNode, True); + // assign new reference item + FRangeAnchor := NewNode; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.HandleDrawSelection(X, Y: TDimension): Boolean; + +// Handles multi-selection with a focus rectangle. +// Result is True if something changed in selection. + +var + OldRect, + NewRect: TRect; + MainColumn: TColumnIndex; + MaxValue: Integer; + + // limits of a node and its text + NodeLeft, + NodeRight: TDimension; + + // alignment and directionality + CurrentBidiMode: TBidiMode; + CurrentAlignment: TAlignment; + +begin + Result := False; + + // Selection changes are only done if the user drew a selection rectangle large + // enough to exceed the threshold. + if (FRoot.TotalCount > 1) and (tsDrawSelecting in FStates) then + begin + // Effective handling of node selection is done by using two rectangles stored in FSelectRec. + OldRect := OrderRect(FLastSelRect); + NewRect := OrderRect(FNewSelRect); + ClearTempCache; + + MainColumn := FHeader.MainColumn; + + // Alignment and bidi mode determine where the node text is located within a node. + if MainColumn <= NoColumn then + begin + CurrentBidiMode := BidiMode; + CurrentAlignment := Alignment; + end + else + begin + CurrentBidiMode := FHeader.Columns[MainColumn].BidiMode; + CurrentAlignment := FHeader.Columns[MainColumn].Alignment; + end; + + // Determine initial left border of first node (take column reordering into account). + if FHeader.UseColumns then + begin + // The mouse coordinates don't include any horizontal scrolling hence take this also + // out from the returned column position. + NodeLeft := FHeader.Columns[MainColumn].Left + FEffectiveOffsetX; + NodeRight := NodeLeft + FHeader.Columns[MainColumn].Width; + end + else + begin + NodeLeft := 0 + FEffectiveOffsetX; + NodeRight := NodeLeft + ClientWidth; + end; + if CurrentBidiMode = bdLeftToRight then + Result := CollectSelectedNodesLTR(MainColumn, NodeLeft, NodeRight, CurrentAlignment, OldRect, NewRect) + else + Result := CollectSelectedNodesRTL(MainColumn, NodeLeft, NodeRight, CurrentAlignment, OldRect, NewRect); + end; + + if Result then + begin + // Do some housekeeping if there was a change. + MaxValue := PackArray(FSelection, FSelectionCount); + if MaxValue > -1 then + begin + FSelectionCount := MaxValue; + SetLength(FSelection, FSelectionCount); + end; + if FTempNodeCount > 0 then + begin + if tsClearOnNewSelection in fStates then + begin + DoStateChange([], [tsClearOnNewSelection]); + ClearSelection(False); + end; + + AddToSelection(FTempNodeCache, FTempNodeCount); + ClearTempCache; + end; + + Change(nil); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.HasVisibleNextSibling(Node: PVirtualNode): Boolean; + +// Helper method to determine if the given node has a visible next sibling. This is needed to +// draw correct tree lines. + +begin + // Check if there is a sibling at all. + Result := Assigned(Node.NextSibling); + + if Result then + begin + repeat + Node := Node.NextSibling; + Result := IsEffectivelyVisible[Node]; + until Result or (Node.NextSibling = nil); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.HasVisiblePreviousSibling(Node: PVirtualNode): Boolean; + +// Helper method to determine if the given node has a visible previous sibling. This is needed to +// draw correct tree lines. + +begin + // Check if there is a sibling at all. + Result := Assigned(Node.PrevSibling); + + if Result then + begin + repeat + Node := Node.PrevSibling; + Result := IsEffectivelyVisible[Node]; + until Result or (Node.PrevSibling = nil); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ImageListChange(Sender: TObject); + +begin + if not (csDestroying in ComponentState) then + Invalidate; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InitializeFirstColumnValues(var PaintInfo: TVTPaintInfo); + +// Determines initial index, position and cell size of the first visible column. + +begin + PaintInfo.Column := FHeader.Columns.GetFirstVisibleColumn; + with FHeader.Columns, PaintInfo do + begin + if Column > NoColumn then + begin + CellRect.Right := CellRect.Left + Items[Column].Width; + Position := Items[Column].Position; + end + else + Position := 0; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InitRecursive(Node: PVirtualNode; Levels: Cardinal = MaxInt; pVisibleOnly: Boolean = True); + +// Initializes a node and optionally its children up to a certain level. +// The sepcified number of levels are latrive to the givne Node. + +var + Run: PVirtualNode; +begin + if not Assigned(Node) then + Node := FRoot; + + if (Node <> FRoot) and not (vsInitialized in Node.States) then + InitNode(Node); + if (Levels = 0) or (pVisibleOnly and not (vsExpanded in Node.States)) then + exit; + Run := Node.FirstChild; + + while Assigned(Run) do + begin + InitRecursive(Run, Levels - 1, pVisibleOnly); + Run := Run.NextSibling; + end; +end; + +procedure TBaseVirtualTree.InitRootNode(OldSize: Cardinal = 0); + +// Reinitializes the root node. + +var + NewSize: Cardinal; + +begin + NewSize := TreeNodeSize + FTotalInternalDataSize; + if FRoot = nil then + FRoot := AllocMem(NewSize) + else + begin + ReallocMem(FRoot, NewSize); + ZeroMemory(PByte(FRoot) + OldSize, NewSize - OldSize); + end; + + with FRoot^ do + begin + // Indication that this node is the root node. + SetPrevSibling(FRoot); + SetNextSibling(FRoot); + SetParent(Pointer(Self)); + States := [vsInitialized, vsExpanded, vsHasChildren, vsVisible]; + TotalHeight := FDefaultNodeHeight; + TotalCount := 1; + SetNodeHeight(FDefaultNodeHeight); + Align := 50; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InterruptValidation(pWaitForValidationTermination: Boolean = True); + +var + WasValidating: Boolean; +begin + DoStateChange([tsStopValidation], [tsUseCache]); + + // Check the worker thread existance. It might already be gone (usually on destruction of the last tree). + WasValidating := (tsValidating in FStates); + TWorkerThread.RemoveTree(Self, pWaitForValidationTermination); + if WasValidating then + InvalidateCache(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsFirstVisibleChild(Parent, Node: PVirtualNode): Boolean; + +// Helper method to check if Node is the same as the first visible child of Parent. + +var + Run: PVirtualNode; + +begin + // Find first visible child. + Run := Parent.FirstChild; + while Assigned(Run) and not IsEffectivelyVisible[Run] do + Run := Run.NextSibling; + + Result := Assigned(Run) and (Run = Node); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsLastVisibleChild(Parent, Node: PVirtualNode): Boolean; + +// Helper method to check if Node is the same as the last visible child of Parent. + +var + Run: PVirtualNode; + +begin + // Find last visible child. + Run := Parent.LastChild; + while Assigned(Run) and not IsEffectivelyVisible[Run] do + Run := Run.PrevSibling; + + Result := Assigned(Run) and (Run = Node); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.MakeNewNode: PVirtualNode; + +var + Size: Cardinal; + +begin + Size := TreeNodeSize; + if (csDesigning in ComponentState) and (FNodeDataSize < 0) then + System.Inc(Size, SizeOf(Pointer)) // Fixes #702 + else + begin // Make sure FNodeDataSize is valid. + if FNodeDataSize < 0 then // NodeDataSize may be 0 for descendant controls that use only InternalData. + ValidateNodeDataSize(FNodeDataSize); + + // Take record alignment into account. + System.Inc(Size, FNodeDataSize); + end; + + Result := AllocMem(Size + FTotalInternalDataSize); + + // Fill in some default values. + with Result^ do + begin + TotalCount := 1; + TotalHeight := FDefaultNodeHeight; + SetNodeHeight(FDefaultNodeHeight); + States := [vsVisible]; + Align := 50; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.PackArray({*}const TheArray: TNodeArray; Count: Integer): Integer; assembler; +// *This is an optimization to get as near as possible with the PUREPASCAL code without the +// compiler generating a _DynArrayAddRef call. We still modify the array's content via pointers. + +// Removes all entries from the selection array which are no longer in use. The selection array must be sorted for this +// algo to work. Values which must be removed are marked with bit 0 (LSB) set. This little trick works because memory +// is always allocated DWORD aligned. Since the selection array must be sorted while determining the entries to be +// removed it is much more efficient to increment the entry in question instead of setting it to nil (which would break +// the ordered appearance of the list). +// +// On enter EAX contains self reference, EDX the address to TheArray and ECX Count +// The returned value is the number of remaining entries in the array, so the caller can reallocate (shorten) +// the selection array if needed or -1 if nothing needs to be changed. + +{$IF Defined(CPUX64) or Defined(VT_FMX)} +var + Source, Dest: ^PVirtualNode; + ConstOne: NativeInt; +begin + Source := Pointer(TheArray); + ConstOne := 1; + Result := 0; + // Do the fastest scan possible to find the first entry + while (Count <> 0) and {not Odd(NativeInt(Source^))} (NativeInt(Source^) and ConstOne = 0) do + begin + System.Inc(Result); + System.Inc(Source); + System.Dec(Count); + end; + + if Count <> 0 then + begin + Dest := Source; + repeat + // Skip odd entries + if {not Odd(NativeInt(Source^))} NativeInt(Source^) and ConstOne = 0 then + begin + Dest^ := Source^; + System.Inc(Result); + System.Inc(Dest); + end; + System.Inc(Source); // Point to the next entry + System.Dec(Count); + until Count = 0; + end; +end; +{$else} +asm + PUSH EBX + PUSH EDI + PUSH ESI + MOV ESI, EDX + MOV EDX, -1 + JCXZ @@Finish // Empty list? + INC EDX // init remaining entries counter + MOV EDI, ESI // source and destination point to the list memory + MOV EBX, 1 // use a register instead of immediate operant to check against +@@PreScan: + TEST [ESI], EBX // do the fastest scan possible to find the first entry + // which must be removed + JNZ @@DoMainLoop + INC EDX + ADD ESI, 4 + DEC ECX + JNZ @@PreScan + JMP @@Finish + +@@DoMainLoop: + MOV EDI, ESI +@@MainLoop: + TEST [ESI], EBX // odd entry? + JNE @@Skip // yes, so skip this one + MOVSD // else move the entry to new location + INC EDX // count the moved entries + DEC ECX + JNZ @@MainLoop // do it until all entries are processed + JMP @@Finish + +@@Skip: + ADD ESI, 4 // point to the next entry + DEC ECX + JNZ @@MainLoop // do it until all entries are processed +@@Finish: + MOV EAX, EDX // prepare return value + POP ESI + POP EDI + POP EBX +end; +{$IFEND} + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.PrepareBitmaps(NeedButtons, NeedLines: Boolean); + +// initializes the contents of the internal bitmaps + +const + LineBitsDotted: array [0..8] of Word = ($55, $AA, $55, $AA, $55, $AA, $55, $AA, $55); + LineBitsSolid: array [0..7] of Word = (0, 0, 0, 0, 0, 0, 0, 0); + +var + Bits: Pointer; + Size: TSize; + Theme: HTHEME; + R: TRect; + BitsLinesCount: Word; + + //--------------- local function -------------------------------------------- + + procedure FillBitmap (ABitmap: TBitmap); + begin + with ABitmap, Canvas do + begin + SetSize(Size.cx, Size.cy); + + if (tsUseThemes in FStates) and (toUseExplorerTheme in FOptions.PaintOptions) or VclStyleEnabled then + begin + if (FHeader.MainColumn > NoColumn) then + Brush.Color := FHeader.Columns[FHeader.MainColumn].GetEffectiveColor + else + Brush.Color := FColors.BackGroundColor; + end + else + Brush.Color := clFuchsia; + + Transparent := True; + TransparentColor := Brush.Color; + + FillRect(Rect(0, 0, Width, Height)); + end; + end; + + //--------------- end local function ---------------------------------------- + +const + cMinExpandoHeight = 11; // pixels @100% +begin + if VclStyleEnabled and (seClient in StyleElements) then + begin + if NeedButtons then begin + if StyleServices.GetElementSize(FPlusBM.Canvas.Handle, StyleServices.GetElementDetails(tcbCategoryGlyphClosed), TElementSize.esActual, Size) then + begin + Size.cx := Max(Size.cx, cMinExpandoHeight); // Use min size of 11, see issue #1035 / RSP-33715 + Size.cx := ScaledPixels(Size.cx) // I would have expected that the returned value is dpi-sclaed, but this is not the case in RAD Studio 10.4.1. See issue #984 + end + else + Size.cx := ScaledPixels(cMinExpandoHeight); + Size.cy := Size.cx; + FillBitmap(FPlusBM); + FillBitmap(FHotPlusBM); + FillBitmap(FSelectedHotPlusBM); + FillBitmap(FMinusBM); + FillBitmap(FHotMinusBM); + FillBitmap(FSelectedHotMinusBM); + R := Rect(0,0,Size. cx,Size.cy); + // tcbCategoryGlyphClosed, tcbCategoryGlyphOpened from CategoryButtons + StyleServices.DrawElement(FPlusBM.Canvas.Handle, StyleServices.GetElementDetails(tcbCategoryGlyphClosed), R {$IF CompilerVersion >= 34}, nil, FCurrentPPI{$IFEND}); + StyleServices.DrawElement(FMinusBM.Canvas.Handle, StyleServices.GetElementDetails(tcbCategoryGlyphOpened), R {$IF CompilerVersion >= 34}, nil, FCurrentPPI{$IFEND}); + FHotMinusBM.Canvas.Draw(0, 0, FMinusBM); + FSelectedHotMinusBM.Canvas.Draw(0, 0, FMinusBM); + FHotPlusBM.Canvas.Draw(0, 0, FPlusBM); + FSelectedHotPlusBM.Canvas.Draw(0, 0, FPlusBM); + if Assigned(FOnPrepareButtonImages) then + FOnPrepareButtonImages(Self, FPlusBM, FHotPlusBM, FSelectedHotPlusBM, FMinusBM, FHotMinusBM, FSelectedHotMinusBM, size); + end;//if NeedButtons + end// if VclStyleEnabled + else + begin // No stlye + Size.cx := ScaledPixels(9); + Size.cy := ScaledPixels(9); + if tsUseThemes in FStates then + begin + R := Rect(0, 0, 100, 100); + {$if CompilerVersion >= 33} + if TOSVersion.Check(10) and (TOSVersion.Build >= 15063) then + Theme := OpenThemeDataForDPI(Handle, 'TREEVIEW', Self.FCurrentPPI) + else + Theme := OpenThemeData(Handle, 'TREEVIEW'); + {$else} + Theme := OpenThemeData(Handle, 'TREEVIEW'); + {$ifend} + GetThemePartSize(Theme, FPlusBM.Canvas.Handle, TVP_GLYPH, GLPS_OPENED, @R, TS_TRUE, Size); + end + else + Theme := 0; + + if NeedButtons then + begin + //VCL Themes do not really have ability to provide tree plus/minus images when not using the + //windows theme. The bitmap style designer doesn't have any elements for them, and you + //cannot name any elements you add, which makes it useless. + //To mitigate this, Hook up the OnPrepareButtonImages and draw them yourself. + if Assigned(FOnPrepareButtonImages) then + begin + FillBitmap(FPlusBM); + FillBitmap(FHotPlusBM); + FillBitmap(FSelectedHotPlusBM); + FillBitmap(FMinusBM); + FillBitmap(FHotMinusBM); + FillBitmap(FSelectedHotMinusBM); + FOnPrepareButtonImages(Self, FPlusBM, FHotPlusBM, FSelectedHotPlusBM, FMinusBM, FHotMinusBM, FSelectedHotMinusBM, size); + end + else + begin + with FMinusBM, Canvas do + begin + // box is always of odd size + FillBitmap(FMinusBM); + FillBitmap(FHotMinusBM); + FillBitmap(FSelectedHotMinusBM); + // Weil die selbstgezeichneten Bitmaps sehen im Vcl Style scheiße aus + // Because the self-drawn bitmaps view Vcl Style shit + if Theme = 0 then + begin + if not(tsUseExplorerTheme in FStates) then + begin + if FButtonStyle = bsTriangle then + begin + FMinusBM.Canvas.Brush.Color := clBlack; + FMinusBM.Canvas.Pen.Color := clBlack; + FMinusBM.Canvas.Polygon([Point(0, 2), Point(8, 2), Point(4, 6)]); + end + else + begin + // Button style is rectangular. Now ButtonFillMode determines how to fill the interior. + if FButtonFillMode in [fmTreeColor, fmWindowColor, fmTransparent] then + begin + case FButtonFillMode of + fmTreeColor: + FMinusBM.Canvas.Brush.Color := FColors.BackGroundColor; + fmWindowColor: + FMinusBM.Canvas.Brush.Color := clWindow; + end; + Pen.Color := FColors.TreeLineColor; + Rectangle(0, 0, Width, Height); + Pen.Color := FColors.NodeFontColor; + MoveTo(2, Width div 2); + LineTo(Width - 2, Width div 2); + end + end; + FHotMinusBM.Canvas.Draw(0, 0, FMinusBM); + FSelectedHotMinusBM.Canvas.Draw(0, 0, FMinusBM); + end; + end; + end; + with FPlusBM, Canvas do + begin + FillBitmap(FPlusBM); + FillBitmap(FHotPlusBM); + FillBitmap(FSelectedHotPlusBM); + if Theme = 0 then + begin + if not(tsUseExplorerTheme in FStates) then + begin + if FButtonStyle = bsTriangle then + begin + FPlusBM.Canvas.Brush.Color := clBlack; + FPlusBM.Canvas.Pen.Color := clBlack; + FPlusBM.Canvas.Polygon([Point(2, 0), Point(6, 4), Point(2, 8)]); + end + else + begin + // Button style is rectangular. Now ButtonFillMode determines how to fill the interior. + if FButtonFillMode in [fmTreeColor, fmWindowColor, fmTransparent] then + begin + case FButtonFillMode of + fmTreeColor: + FPlusBM.Canvas.Brush.Color := FColors.BackGroundColor; + fmWindowColor: + FPlusBM.Canvas.Brush.Color := clWindow; + end; + Pen.Color := FColors.TreeLineColor; + Rectangle(0, 0, Width, Height); + Pen.Color := FColors.NodeFontColor; + MoveTo(2, Width div 2); + LineTo(Width - 2, Width div 2); + MoveTo(Width div 2, 2); + LineTo(Width div 2, Width - 2); + end + end; + FHotPlusBM.Canvas.Draw(0, 0, FPlusBM); + FSelectedHotPlusBM.Canvas.Draw(0, 0, FPlusBM); + end; + end; + end; + + + // Overwrite glyph images if theme is active. + if (tsUseThemes in FStates) and (Theme <> 0) then + begin + R := Rect(0, 0, Size.cx, Size.cy); + DrawThemeBackground(Theme, FPlusBM.Canvas.Handle, TVP_GLYPH, GLPS_CLOSED, R, nil); + DrawThemeBackground(Theme, FMinusBM.Canvas.Handle, TVP_GLYPH, GLPS_OPENED, R, nil); + if tsUseExplorerTheme in FStates then + begin + DrawThemeBackground(Theme, FHotPlusBM.Canvas.Handle, TVP_HOTGLYPH, GLPS_CLOSED, R, nil); + DrawThemeBackground(Theme, FSelectedHotPlusBM.Canvas.Handle, TVP_HOTGLYPH, GLPS_CLOSED, R, nil); + DrawThemeBackground(Theme, FHotMinusBM.Canvas.Handle, TVP_HOTGLYPH, GLPS_OPENED, R, nil); + DrawThemeBackground(Theme, FSelectedHotMinusBM.Canvas.Handle, TVP_HOTGLYPH, GLPS_OPENED, R, nil); + end + else + begin + FHotPlusBM.Canvas.Draw(0, 0, FPlusBM); + FSelectedHotPlusBM.Canvas.Draw(0, 0, FPlusBM); + FHotMinusBM.Canvas.Draw(0, 0, FMinusBM); + FSelectedHotMinusBM.Canvas.Draw(0, 0, FMinusBM); + end; + end; + end; + if tsUseThemes in FStates then + CloseThemeData(Theme); + end;// if NeedButtons + end;// else + + if NeedLines then + begin + case FLineStyle of + lsDotted: + begin + Bits := @LineBitsDotted; + BitsLinesCount:= Length(LineBitsDotted); + end; + lsSolid: + begin + Bits := @LineBitsSolid; + BitsLinesCount:= Length(LineBitsSolid); + end; + else // lsCustomStyle + Bits := @LineBitsDotted; + DoGetLineStyle(Bits); + BitsLinesCount:= Length(LineBitsDotted); + end; + DottedBrushTreeLines:= PrepareDottedBrush(DottedBrushTreeLines, Bits, BitsLinesCount); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetAlignment(const Value: TAlignment); + +begin + if FAlignment <> Value then + begin + FAlignment := Value; + if not (csLoading in ComponentState) then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetAnimationDuration(const Value: Cardinal); + +begin + FAnimationDuration := Value; + if FAnimationDuration = 0 then + FOptions.AnimationOptions := FOptions.AnimationOptions - [toAnimatedToggle] + else + FOptions.AnimationOptions := FOptions.AnimationOptions + [toAnimatedToggle] +end; + +//---------------------------------------------------------------------------------------------------------------------- +{ New, Support for transparent background: + * Image types: BMP, PNG, GIF, ICO, EMF, TIFF and WMF are automatically identified to support transparent background + * Also detects certain third party image classes registered for PNG, GIF and other image types so that the + transparency related code is used for them. See the code below. + * If some other third party image class is registered that is not detected, + set the flag BackgroundTransparentExternalType explicitly in order to properly do + transparent painting. +} +procedure TBaseVirtualTree.SetBackground(const Value: TVTBackground); + +begin + FBackground.Assign(Value); + Invalidate; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetBackGroundImageTransparent(const Value: Boolean); + +begin + if Value <> FBackGroundImageTransparent then + begin + FBackGroundImageTransparent := Value; + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetBackgroundOffset(const Index: Integer; const Value: TDimension); + +begin + case Index of + 0: + if FBackgroundOffsetX <> Value then + begin + FBackgroundOffsetX := Value; + Invalidate; + end; + 1: + if FBackgroundOffsetY <> Value then + begin + FBackgroundOffsetY := Value; + Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetBorderStyle(Value: TBorderStyle); + +begin + if FBorderStyle <> Value then + begin + FBorderStyle := Value; + RecreateWnd; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetBottomNode(Node: PVirtualNode); + +var + Run: PVirtualNode; + R: TRect; + +begin + if Assigned(Node) then + begin + // make sure all parents of the node are expanded + Run := Node.Parent; + while Run <> FRoot do + begin + if not (vsExpanded in Run.States) then + ToggleNode(Run); + Run := Run.Parent; + end; + R := GetDisplayRect(Node, FHeader.MainColumn, True); + DoSetOffsetXY(Point(FOffsetX, FOffsetY + ClientHeight - R.Top - NodeHeight[Node]), + [suoRepaintScrollBars, suoUpdateNCArea]); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetBottomSpace(const Value: TDimension); + +begin + if FBottomSpace <> Value then + begin + FBottomSpace := Value; + UpdateVerticalScrollBar(True); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetButtonFillMode(const Value: TVTButtonFillMode); + +begin + if FButtonFillMode <> Value then + begin + if Value = TVTButtonFillMode.fmShaded then // no longer supported + FButtonFillMode := TVTButtonFillMode.fmTreeColor + else + FButtonFillMode := Value; + if not (csLoading in ComponentState) then + begin + PrepareBitmaps(True, False); + if HandleAllocated then + Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetButtonStyle(const Value: TVTButtonStyle); + +begin + if FButtonStyle <> Value then + begin + FButtonStyle := Value; + if not (csLoading in ComponentState) then + begin + PrepareBitmaps(True, False); + if HandleAllocated then + Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetCheckState(Node: PVirtualNode; Value: TCheckState); + +begin + if (Node.CheckState <> Value) and DoChecking(Node, Value) then + DoCheckClick(Node, Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetCheckStateForAll(aCheckState: TCheckState; pSelectedOnly: Boolean; pExcludeDisabled: Boolean = True); + +// Changes the check state for all or for all seledcted nodes. +// aCheckState: The new check state. +// pSelectedOnly: If passed True, only the selected nodes will bechnaged, if passed False all nodes in the control will be changed. +// pExcludeDisabled: Optiopnal. If passed True (the default value), disabled checkboxes won't be changed, if passed False disabled checkboxes will be altered too. + +var + lItem : PVirtualNode; +begin + With Self do begin + Screen.Cursor := crHourGlass; + BeginUpdate; + try + if pSelectedOnly then + lItem := GetFirstSelected + else + lItem := GetFirst; + //for i:=0 to List.Items.Count-1 do begin + while Assigned(lItem) do begin + if not pExcludeDisabled or not CheckState[lItem].IsDisabled() then + CheckState[lItem] := aCheckState; + if pSelectedOnly then + lItem := GetNextSelected(lItem) + else + lItem := GetNext(lItem); + end;//while + finally + Screen.Cursor := crDefault; + EndUpdate; + end;//try..finally + end;//With +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetCheckType(Node: PVirtualNode; Value: TCheckType); + +begin + if (Node.CheckType <> Value) and not (toReadOnly in FOptions.MiscOptions) then + begin + Node.CheckType := Value; + if (Value <> ctTriStateCheckBox) and (Node.CheckState in [csMixedNormal, csMixedPressed]) then + Node.CheckState := csUncheckedNormal;// reset check state if it doesn't fit the new check type + // For check boxes with tri-state check box parents we have to initialize differently. + if (toAutoTriStateTracking in FOptions.AutoOptions) and (Value in [ctCheckBox, ctTriStateCheckBox]) and + (Node.Parent <> FRoot) then + begin + if not (vsInitialized in Node.Parent.States) then + InitNode(Node.Parent); + if (Node.Parent.CheckType = ctTriStateCheckBox) then begin + if (GetCheckState(Node.Parent) in [csUncheckedNormal, csUncheckedDisabled]) then + CheckState[Node] := csUncheckedNormal + else if (GetCheckState(Node.Parent) in [csCheckedNormal, csCheckedDisabled]) then + CheckState[Node] := csCheckedNormal; + end;//if + end;//if + InvalidateNode(Node); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetChildCount(Node: PVirtualNode; NewChildCount: Cardinal); + +// Changes a node's child structure to accomodate the new child count. This is used to add or delete +// child nodes to/from the end of the node's child list. To insert or delete a specific node a separate +// routine is used. + +var + Remaining: Cardinal; + Index: Cardinal; + Child: PVirtualNode; + Count: Integer; + NewHeight: TNodeHeight; +begin + if not (toReadOnly in FOptions.MiscOptions) then + begin + if Node = nil then + Node := FRoot; + + Assert(GetCurrentThreadId = MainThreadId, 'UI controls may only be changed in UI thread.'); + if NewChildCount = 0 then + DeleteChildren(Node) + else + begin + // If nothing changed then do nothing. + if NewChildCount <> Node.ChildCount then + begin + InterruptValidation; + + if NewChildCount > Node.ChildCount then + begin + Remaining := NewChildCount - Node.ChildCount; + Count := Remaining; + NewHeight := Node.TotalHeight; + + // New nodes to add. + if Assigned(Node.LastChild) then + Index := Node.LastChild.Index + 1 + else + begin + Index := 0; + Include(Node.States, vsHasChildren); + end; + Node.States := Node.States - [vsAllChildrenHidden, vsHeightMeasured]; + if (vsExpanded in Node.States) and FullyVisible[Node] then + System.Inc(FVisibleCount, Count); // Do this before a possible init of the sub-nodes in DoMeasureItem() + + // New nodes are by default always visible, so we don't need to check the visibility. + while Remaining > 0 do + begin + Child := MakeNewNode; + Child.SetIndex(Index); + Child.SetPrevSibling(Node.LastChild); + if Assigned(Node.LastChild) then + Node.LastChild.SetNextSibling(Child); + Child.SetParent(Node); + Node.SetLastChild(Child); + if Node.FirstChild = nil then + Node.SetFirstChild(Child); + System.Dec(Remaining); + System.Inc(Index); + + if (toVariableNodeHeight in FOptions.MiscOptions) then + GetNodeHeight(Child); + Inc(NewHeight, Child.TotalHeight); + end; + + if vsExpanded in Node.States then + AdjustTotalHeight(Node, NewHeight, False); + + AdjustTotalCount(Node, Count, True); + Node.SetChildCount(NewChildCount); + if (FUpdateCount = 0) and (toAutoSort in FOptions.AutoOptions) and (FHeader.SortColumn > InvalidColumn) then + Sort(Node, FHeader.SortColumn, FHeader.SortDirection, True); + + InvalidateCache; + end//if NewChildCount > Node.ChildCount + else + begin + // Nodes have to be deleted. + Remaining := Node.ChildCount - NewChildCount; + while Remaining > 0 do + begin + DeleteNode(Node.LastChild); + System.Dec(Remaining); + end; + end; + + if FUpdateCount = 0 then + begin + ValidateCache; + UpdateScrollBars(True); + Invalidate; + end; + + if Node = FRoot then + StructureChange(nil, crChildAdded) + else + StructureChange(Node, crChildAdded); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetClipboardFormats(const Value: TClipboardFormats); + +var + I: Integer; + +begin + // Add string by string instead doing an Assign or AddStrings because the list may return -1 for + // invalid entries which cause trouble for the standard implementation. + FClipboardFormats.Clear; + for I := 0 to Value.Count - 1 do + FClipboardFormats.Add(Value[I]); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetColors(const Value: TVTColors); + +begin + FColors.Assign(Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetCheckImageKind(Value: TCheckImageKind); +begin + if (Value < Low(Value)) or (Value> High(Value)) then + Value := ckSystemDefault; + // property is deprecated. See issue #622 + if FCheckImageKind <> Value then + begin + if FCheckImageKind = ckSystemDefault then + FreeAndNil(FCheckImages); + FCheckImageKind := Value; + if Value = ckCustom then + FCheckImages := FCustomCheckImages + else if HandleAllocated then + FCheckImages := CreateSystemImageSet(); + if HandleAllocated and (FUpdateCount = 0) and not (csLoading in ComponentState) then + InvalidateRect(nil, False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetCustomCheckImages(const Value: TCustomImageList); + +begin + if FCustomCheckImages <> Value then + begin + if Assigned(FCustomCheckImages) then + begin + FCustomCheckImages.UnRegisterChanges(FCustomCheckChangeLink); + FCustomCheckImages.RemoveFreeNotification(Self); + // Reset the internal check image list reference too, if necessary. + if FCheckImages = FCustomCheckImages then + FCheckImages := nil; + end; + FCustomCheckImages := Value; + if Assigned(FCustomCheckImages) then + begin + // If custom check images are assigned, we switch the property CheckImageKind to ckCustom so that they are actually used + CheckImageKind := ckCustom; + FCustomCheckImages.RegisterChanges(FCustomCheckChangeLink); + FCustomCheckImages.FreeNotification(Self); + end + else + CheckImageKind := ckSystemDefault; + if not (csLoading in ComponentState) then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetDefaultNodeHeight(Value: TDimension); + +begin + if Value = 0 then + Value := cInitialDefaultNodeHeight; + if FDefaultNodeHeight <> Value then + begin + Inc(FRoot.TotalHeight, Value - FDefaultNodeHeight); + FRoot.SetNodeHeight(FRoot.NodeHeight + Value - FDefaultNodeHeight); + FDefaultNodeHeight := Value; + InvalidateCache; + if (FUpdateCount = 0) and HandleAllocated and not (csLoading in ComponentState) then + begin + ValidateCache; + UpdateScrollBars(True); + ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions, True); + Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetDisabled(Node: PVirtualNode; Value: Boolean); + +begin + if Assigned(Node) and (Value xor (vsDisabled in Node.States)) then + begin + if Value then + Include(Node.States, vsDisabled) + else + Exclude(Node.States, vsDisabled); + + if FUpdateCount = 0 then + InvalidateNode(Node); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetDoubleBuffered(const Value: Boolean); +begin + // empty by intention, we do our own buffering +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetDoubleBuffered: Boolean; +begin + Result := True; // we do our own buffering +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetEmptyListMessage(const Value: string); + +begin + if Value <> EmptyListMessage then + begin + FEmptyListMessage := Value; + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetExpanded(Node: PVirtualNode; Value: Boolean); + +begin + if Assigned(Node) and (Node <> FRoot) and (Value xor (vsExpanded in Node.States)) then + ToggleNode(Node); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetFocusedColumn(Value: TColumnIndex); + +begin + if (FFocusedColumn <> Value) and + DoFocusChanging(FFocusedNode, FFocusedNode, FFocusedColumn, Value) then + begin + CancelEditNode; + InvalidateColumn(FFocusedColumn); + InvalidateColumn(Value); + FFocusedColumn := Value; + if Assigned(FFocusedNode) and not (toDisableAutoscrollOnFocus in FOptions.AutoOptions) then + begin + if ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions, True) then + InvalidateNode(FFocusedNode); + end; + + if Assigned(FDropTargetNode) then + InvalidateNode(FDropTargetNode); + + DoFocusChange(FFocusedNode, FFocusedColumn); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetFocusedNode(Value: PVirtualNode); + +var + WasDifferent: Boolean; + +begin + WasDifferent := Value <> FFocusedNode; + DoFocusNode(Value, True); + // Do change event only if there was actually a change. + if WasDifferent and (FFocusedNode = Value) then + DoFocusChange(FFocusedNode, FFocusedColumn); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetFullyVisible(Node: PVirtualNode; Value: Boolean); + +// This method ensures that a node is visible and all its parent nodes are expanded and also visible +// if Value is True. Otherwise the visibility flag of the node is reset but the expand state +// of the parent nodes stays untouched. + +begin + Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter'); + + IsVisible[Node] := Value; + if Value then + begin + repeat + Node := Node.Parent; + if Node = FRoot then + Break; + if not (vsExpanded in Node.States) then + ToggleNode(Node); + if not (vsVisible in Node.States) then + IsVisible[Node] := True; + until False; + end; + ScrollIntoView(Node, False); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetHasChildren(Node: PVirtualNode; Value: Boolean); + +begin + if Assigned(Node) and not (toReadOnly in FOptions.MiscOptions) then + begin + if Value then + Include(Node.States, vsHasChildren) + else + begin + Exclude(Node.States, vsHasChildren); + DeleteChildren(Node); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetHeader(const Value: TVTHeader); + +begin + FHeader.Assign(Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetHotNode(Value: PVirtualNode); + +begin + FCurrentHotNode := Value; +end; + + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetFiltered(Node: PVirtualNode; Value: Boolean); + +// Sets the 'filtered' flag of the given node according to Value and updates all dependent states. + +var + NeedUpdate: Boolean; + +begin + Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); + + // Initialize the node if necessary as this might change the filtered state. + if not (vsInitialized in Node.States) then + InitNode(Node); + + if Value <> (vsFiltered in Node.States) then + begin + InterruptValidation; + NeedUpdate := False; + if Value then + begin + Include(Node.States, vsFiltered); + if not (toShowFilteredNodes in FOptions.PaintOptions) then + begin + if (vsInitializing in Node.States) and not (vsHasChildren in Node.States) then + AdjustTotalHeight(Node, 0, False) + else + AdjustTotalHeight(Node, -NodeHeight[Node], True); + if FullyVisible[Node] then + begin + System.Dec(FVisibleCount); + NeedUpdate := True; + end; + if FocusedNode = Node then + FocusedNode := nil; + end; + + if FUpdateCount = 0 then + DetermineHiddenChildrenFlag(Node.Parent) + else + Include(FStates, tsUpdateHiddenChildrenNeeded); + end + else + begin + Exclude(Node.States, vsFiltered); + if not (toShowFilteredNodes in FOptions.PaintOptions) then + begin + AdjustTotalHeight(Node, NodeHeight[Node], True); + if FullyVisible[Node] then + begin + System.Inc(FVisibleCount); + NeedUpdate := True; + end; + end; + + if vsVisible in Node.States then + // Update the hidden children flag of the parent. + // Since this node is now visible we simply have to remove the flag. + Exclude(Node.Parent.States, vsAllChildrenHidden); + end; + + InvalidateCache; + if NeedUpdate and (FUpdateCount = 0) then + begin + ValidateCache; + UpdateScrollBars(True); + Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- +procedure TBaseVirtualTree.SetImages(const Value: TCustomImageList); + +begin + if FImages <> Value then + begin + if Assigned(FImages) then + begin + FImages.UnRegisterChanges(FImageChangeLink); + FImages.RemoveFreeNotification(Self); + end; + FImages := Value; + if Assigned(FImages) then + begin + FImages.RegisterChanges(FImageChangeLink); + FImages.FreeNotification(Self); + end; + if not (csLoading in ComponentState) then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetIndent(Value: TDimension); + +begin + if FIndent <> Value then + begin + FIndent := Value; + if not (csLoading in ComponentState) and (FUpdateCount = 0) and HandleAllocated then + begin + UpdateScrollBars(True); + Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetLineMode(const Value: TVTLineMode); + +begin + if FLineMode <> Value then + begin + FLineMode := Value; + if HandleAllocated and not (csLoading in ComponentState) then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetLineStyle(const Value: TVTLineStyle); + +begin + if FLineStyle <> Value then + begin + FLineStyle := Value; + if not (csLoading in ComponentState) then + begin + PrepareBitmaps(False, True); + if HandleAllocated then + Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetMargin(Value: TDimension); + +begin + if FMargin <> Value then + begin + FMargin := Value; + if HandleAllocated and not (csLoading in ComponentState) then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetMultiline(Node: PVirtualNode; const Value: Boolean); + +begin + if Assigned(Node) and (Node <> FRoot) then + if Value <> (vsMultiline in Node.States) then + begin + if Value then + Include(Node.States, vsMultiline) + else + Exclude(Node.States, vsMultiline); + + if FUpdateCount = 0 then + InvalidateNode(Node); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetNodeAlignment(const Value: TVTNodeAlignment); + +begin + if FNodeAlignment <> Value then + begin + FNodeAlignment := Value; + if HandleAllocated and not (csReading in ComponentState) then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetNodeData(pNode: PVirtualNode; pUserData: Pointer); + + // Can be used to set user data of a PVirtualNode with the size of a pointer, useful for setting + // A pointer to a record or a reference to a class instance. + +var + NodeData: PPointer; +begin + // Check if there is initial user data and there is also enough user data space allocated. + Assert(FNodeDataSize >= SizeOf(Pointer), Self.Classname + ': Cannot set initial user data because there is not enough user data space allocated.'); + NodeData := pNode.GetData(); + NodeData^ := pUserData; + Include(pNode.States, vsOnFreeNodeCallRequired); +end; + +procedure TBaseVirtualTree.SetNodeData(pNode: PVirtualNode; pUserData: T); + + // Can be used to set user data of a PVirtualNode to a class instance. + +begin + pNode.SetData(pUserData); +end; + +procedure TBaseVirtualTree.SetNodeData(pNode: PVirtualNode; const pUserData: IInterface); + + // Can be used to set user data of a PVirtualNode to a class instance, + // will take care about reference counting. + +begin + pNode.SetData(pUserData); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetNodeDataSize(Value: Integer); + +var + LastRootCount: Cardinal; + +begin + if Value < -1 then + Value := -1; + if FNodeDataSize <> Value then + begin + FNodeDataSize := Value; + if not (csLoading in ComponentState) and not (csDesigning in ComponentState) then + begin + LastRootCount := FRoot.ChildCount; + Clear; + SetRootNodeCount(LastRootCount); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetNodeHeight(Node: PVirtualNode; Value: TNodeHeight); + +var + Difference: TDimension; + +begin + Assert(Assigned(Node), 'SetNodeHeight() cannot be called with Node = nil'); + Assert((Node <> FRoot), 'SetNodeHeight() cannot be called for the root node FRoot'); + if (Node.NodeHeight <> Value) then + begin + Difference := Value - Node.NodeHeight; + Node.SetNodeHeight(Value); + + // If the node is effectively filtered out, nothing else has to be done, as it is not visible anyway. + if not IsEffectivelyFiltered[Node] then + begin + AdjustTotalHeight(Node, Difference, True); + + // If an edit operation is currently active then update the editors boundaries as well. + UpdateEditBounds; + + InvalidateCache; + // Stay away from touching the node cache while it is being validated. + if not (tsValidating in FStates) and FullyVisible[Node] then + begin + if (FUpdateCount = 0) and ([tsPainting, tsSizing] * FStates = []) then + begin + ValidateCache; + InvalidateToBottom(Node); + UpdateScrollBars(True); + end; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetNodeParent(Node: PVirtualNode; const Value: PVirtualNode); + +begin + if Assigned(Node) and Assigned(Value) and (Node.Parent <> Value) then + MoveTo(Node, Value, amAddChildLast, False); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetOffsetX(const Value: TDimension); + +begin + DoSetOffsetXY(Point(Value, FOffsetY), DefaultScrollUpdateFlags); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetOffsetXY(const Value: TPoint); + +begin + DoSetOffsetXY(Value, DefaultScrollUpdateFlags); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetOffsetY(const Value: TDimension); + +begin + DoSetOffsetXY(Point(FOffsetX, Value), DefaultScrollUpdateFlags); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetOnPrepareButtonImages(const Value: TVTPrepareButtonImagesEvent); +begin + FOnPrepareButtonImages := Value; + PrepareBitmaps(True, False); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetOptions(const Value: TCustomVirtualTreeOptions); + +begin + FOptions.Assign(Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetRangeX(value: TDimension); +begin + FRangeX := value; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetRootNodeCount(Value: Cardinal); + +begin + // Don't set the root node count until all other properties (in particular the OnInitNode event) have been set. + if csLoading in ComponentState then + begin + FRoot.SetChildCount(Value); + DoStateChange([tsNeedRootCountUpdate]); + end + else + if FRoot.ChildCount <> Value then + begin + BeginUpdate; + InterruptValidation; + SetChildCount(FRoot, Value); + EndUpdate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetScrollBarOptions(Value: TScrollBarOptions); + +begin + FScrollBarOptions.Assign(Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetSearchOption(const Value: TVTIncrementalSearch); + +begin + if FIncrementalSearch <> Value then + begin + FIncrementalSearch := Value; + if FIncrementalSearch = isNone then + begin + StopTimer(SearchTimer); + FSearchBuffer := ''; + FLastSearchNode := nil; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetSelected(Node: PVirtualNode; Value: Boolean); + +begin + if not FSelectionLocked and Assigned(Node) and (Node <> FRoot) and (Value xor (vsSelected in Node.States)) then + begin + if Value then + begin + if FSelectionCount = 0 then + FRangeAnchor := Node + else begin + if not (toMultiSelect in FOptions.SelectionOptions) then + ClearSelection; + if FRangeAnchor = nil then + FRangeAnchor := Node; + end; + + AddToSelection(Node, True); + + if not (toMultiSelect in FOptions.SelectionOptions) then + FocusedNode := GetFirstSelected; // if only one node can be selected, make sure the focused node changes with the selected node + // Make sure there is a valid column selected (if there are columns at all). + if ((FFocusedColumn < 0) or not (coVisible in FHeader.Columns[FFocusedColumn].Options)) and + (FHeader.MainColumn > NoColumn) then + if ([coVisible, coAllowFocus] * FHeader.Columns[FHeader.MainColumn].Options = [coVisible, coAllowFocus]) then + FFocusedColumn := FHeader.MainColumn + else + FFocusedColumn := FHeader.Columns.GetFirstVisibleColumn(True); + end + else + begin + RemoveFromSelection(Node); + if FSelectionCount = 0 then + ResetRangeAnchor; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetSelectionCurveRadius(const Value: Cardinal); + +begin + if FSelectionCurveRadius <> Value then + begin + FSelectionCurveRadius := Value; + if HandleAllocated and not (csLoading in ComponentState) then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetStateImages(const Value: TCustomImageList); + +begin + if FStateImages <> Value then + begin + if Assigned(FStateImages) then + begin + FStateImages.UnRegisterChanges(FStateChangeLink); + FStateImages.RemoveFreeNotification(Self); + end; + FStateImages := Value; + if Assigned(FStateImages) then + begin + FStateImages.RegisterChanges(FStateChangeLink); + FStateImages.FreeNotification(Self); + end; + if HandleAllocated and not (csLoading in ComponentState) then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetTextMargin(Value: TDimension); + +begin + if FTextMargin <> Value then + begin + FTextMargin := Value; + if not (csLoading in ComponentState) then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetTopNode(Node: PVirtualNode); + +var + R: TRect; + Run: PVirtualNode; + +begin + if Assigned(Node) then + begin + // make sure all parents of the node are expanded + Run := Node.Parent; + while Run <> FRoot do + begin + if not (vsExpanded in Run.States) then + ToggleNode(Run); + Run := Run.Parent; + end; + R := GetDisplayRect(Node, FHeader.MainColumn, True); + SetOffsetY(FOffsetY - R.Top); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetUpdateState(Updating: Boolean); + +begin + // The check for visibility is necessary otherwise the tree is automatically shown when + // updating is allowed. As this happens internally the VCL does not get notified and + // still assumes the control is hidden. This results in weird "cannot focus invisible control" errors. + if Visible and HandleAllocated and (FUpdateCount = 0) then + SendWM_SETREDRAW(Updating); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetVerticalAlignment(Node: PVirtualNode; Value: Byte); + +begin + if Value > 100 then + Value := 100; + if Node.Align <> Value then + begin + Node.Align := Value; + if FullyVisible[Node] and not IsEffectivelyFiltered[Node] then + InvalidateNode(Node); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetVisible(Node: PVirtualNode; Value: Boolean); + +// Sets the visibility style of the given node according to Value. + +var + NeedUpdate: Boolean; + +begin + Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); + + if Value <> (vsVisible in Node.States) then + begin + InterruptValidation; + NeedUpdate := False; + if Value then + begin + Include(Node.States, vsVisible); + if vsExpanded in Node.Parent.States then + AdjustTotalHeight(Node.Parent, Node.TotalHeight, True); + if VisiblePath[Node] then + begin + System.Inc(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1))); + NeedUpdate := True; + end; + + // Update the hidden children flag of the parent. + // Since this node is now visible we simply have to remove the flag. + if not IsEffectivelyFiltered[Node] then + Exclude(Node.Parent.States, vsAllChildrenHidden); + end + else + begin + if vsExpanded in Node.Parent.States then + AdjustTotalHeight(Node.Parent, -Node.TotalHeight, True); + if VisiblePath[Node] then + begin + System.Dec(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1))); + NeedUpdate := True; + end; + Exclude(Node.States, vsVisible); + + if FUpdateCount = 0 then + DetermineHiddenChildrenFlag(Node.Parent) + else + Include(FStates, tsUpdateHiddenChildrenNeeded); + end; + + InvalidateCache; + if NeedUpdate and (FUpdateCount = 0) then + begin + ValidateCache; + UpdateScrollBars(True); + Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetVisiblePath(Node: PVirtualNode; Value: Boolean); + +// If Value is True then all parent nodes of Node are expanded. + +begin + Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); + + if Value then + begin + repeat + Node := Node.Parent; + if Node = FRoot then + Break; + if not (vsExpanded in Node.States) then + ToggleNode(Node); + until False; + end; +end; + +// ---------------------------------------------------------------------------------------------------------------------- +procedure TBaseVirtualTree.PrepareBackGroundPicture(Source: TVTBackground; + DrawingBitmap: TBitmap; DrawingBitmapWidth: TDimension; DrawingBitmapHeight: TDimension; ABkgcolor: TColor); +const + DST = $00AA0029; // Ternary Raster Operation - Destination unchanged + + // fill background will work for transparent images and + // will not disturb non-transparent ones + procedure FillDrawBitmapWithBackGroundColor; + begin + DrawingBitmap.Canvas.Brush.Color := ABkgcolor; + DrawingBitmap.Canvas.FillRect(Rect(0, 0, DrawingBitmap.Width, DrawingBitmap.Height)); + end; + +begin + DrawingBitmap.SetSize(DrawingBitmapWidth, DrawingBitmapHeight); + + if (Source.Graphic is TBitmap) and + (FBackGroundImageTransparent or Source.Bitmap.TRANSPARENT) + then + begin + FillDrawBitmapWithBackGroundColor; + MaskBlt(DrawingBitmap.Canvas.Handle, 0, 0, Source.Width, Source.Height, + Source.Bitmap.Canvas.Handle, 0, 0, Source.Bitmap.MaskHandle, 0, 0, + MakeROP4(DST, SRCCOPY)); + end + else + begin + // Similar to TImage's Transparent property behavior, we don't want + // to draw transparent if the following flag is OFF. + if FBackGroundImageTransparent then + FillDrawBitmapWithBackGroundColor; + DrawingBitmap.Canvas.Draw(0, 0, Source.Graphic); + end +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.StaticBackground(Source: TVTBackground; Target: TCanvas; OffsetPosition: TPoint; R: TRect; aBkgColor: TColor); + +// Draws the given source graphic so that it stays static in the given rectangle which is relative to the target bitmap. +// The graphic is aligned so that it always starts at the upper left corner of the target canvas. +// Offset gives the position of the target window as a possible superordinated surface. + +const + DST = $00AA0029; // Ternary Raster Operation - Destination unchanged + +var + PicRect: TRect; + AreaRect: TRect; + DrawRect: TRect; + DrawingBitmap: TBitmap; +begin + DrawingBitmap := TBitmap.Create; + try + // clear background + Target.Brush.Color := aBkgColor; + Target.FillRect(R); + + // Picture rect in relation to client viewscreen. + PicRect := Rect(FBackgroundOffsetX, FBackgroundOffsetY, FBackgroundOffsetX + Source.Width, FBackgroundOffsetY + Source.Height); + + // Area to be draw in relation to client viewscreen. + AreaRect := Rect(OffsetPosition.X + R.Left, OffsetPosition.Y + R.Top, OffsetPosition.X + R.Right, OffsetPosition.Y + R.Bottom); + + // If picture falls in AreaRect, return intersection (DrawRect). + if IntersectRect(DrawRect, PicRect, AreaRect) then + begin + PrepareBackGroundPicture(Source, DrawingBitmap, Source.Width, Source.Height, aBkgColor); + // copy image to destination + BitBlt(Target.Handle, DrawRect.Left - OffsetPosition.X, DrawRect.Top - OffsetPosition.Y, (DrawRect.Right - OffsetPosition.X) - (DrawRect.Left - OffsetPosition.X), + (DrawRect.Bottom - OffsetPosition.Y) - (DrawRect.Top - OffsetPosition.Y) + R.Top, DrawingBitmap.Canvas.Handle, DrawRect.Left - PicRect.Left, DrawRect.Top - PicRect.Top, + SRCCOPY); + end; + finally + DrawingBitmap.Free; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.StopTimer(ID: Integer); + +begin + if HandleAllocated then + KillTimer(Handle, ID); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetWindowTheme(const Theme: string); + +begin + FChangingTheme := True; + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +//used by TCustomVirtualTreeOptions +procedure TBaseVirtualTree.SetVisibleCount(value : Cardinal); +begin + FVisibleCount := value; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.TileBackground(Source: TVTBackground; Target: TCanvas; Offset: TPoint; R: TRect; aBkgColor: TColor); + +// Draws the given source graphic so that it tiles into the given rectangle which is relative to the target bitmap. +// The graphic is aligned so that it always starts at the upper left corner of the target canvas. +// Offset gives the position of the target window in an possible superordinated surface. + +var + SourceX, + SourceY, + TargetX, + DeltaY: TDimension; + DrawingBitmap: TBitmap; +begin + DrawingBitmap := TBitmap.Create; + try + PrepareBackGroundPicture(Source, DrawingBitmap, Source.Width, Source.Height, aBkgColor); + with Target do + begin + SourceY := (R.Top + Offset.Y + FBackgroundOffsetY) mod Source.Height; + // Always wrap the source coordinates into positive range. + if SourceY < 0 then + SourceY := Source.Height + SourceY; + + // Tile image vertically until target rect is filled. + while R.Top < R.Bottom do + begin + SourceX := (R.Left + Offset.X + FBackgroundOffsetX) mod Source.Width; + // always wrap the source coordinates into positive range + if SourceX < 0 then + SourceX := Source.Width + SourceX; + + TargetX := R.Left; + // height of strip to draw + DeltaY := Min(R.Bottom - R.Top, Source.Height - SourceY); + + // tile the image horizontally + while TargetX < R.Right do + begin + BitBlt(Handle, TargetX, R.Top, Min(R.Right - TargetX, Source.Width - SourceX), DeltaY, + DrawingBitmap.Canvas.Handle, SourceX, SourceY, SRCCOPY); + Inc(TargetX, Source.Width - SourceX); + SourceX := 0; + end; + Inc(R.Top, Source.Height - SourceY); + SourceY := 0; + end; + end; + finally + DrawingBitmap.Free; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ToggleCallback(Step, StepSize: Integer; Data: Pointer): Boolean; + +var + Column: TColumnIndex; + Run: TRect; + SecondaryStepSize: Integer; + + //--------------- local functions ------------------------------------------- + + procedure EraseLine; + + var + LocalBrush: TBrush; + + begin + with TToggleAnimationData(Data^), FHeader.Columns do + begin + // Iterate through all columns and erase background in their local color. + // LocalBrush is a brush in the color of the particular column. + Column := GetFirstVisibleColumn; + while (Column > InvalidColumn) and (Run.Left < ClientWidth) do + begin + GetColumnBounds(Column, Run.Left, Run.Right); + if coParentColor in Items[Column].Options then + begin + DC.Brush := Brush; + DC.FillRect(Run); + end + else + begin + LocalBrush := TBrush.Create; + if VclStyleEnabled then + LocalBrush.Color := FColors.BackGroundColor + else + LocalBrush.Color := Items[Column].Color; + DC.Brush := LocalBrush; + DC.FillRect(Run); + LocalBrush.Free; + end; + Column := GetNextVisibleColumn(Column); + end; + end; + end; + + //--------------------------------------------------------------------------- + + procedure DoScrollUp(DC: TCanvas; Brush: TBrush; Area: TRect; Steps: Integer); + + begin + ScrollDC(DC.Handle, 0, -Steps, Area, Area, 0, nil); + + if Step = 0 then + if not FHeader.UseColumns then + begin + DC.Brush := Brush; + DC.FillRect(Rect(Area.Left, Area.Bottom - Steps - 1, Area.Right, Area.Bottom)); + end + else + begin + Run := Rect(Area.Left, Area.Bottom - Steps - 1, Area.Right, Area.Bottom); + EraseLine; + end; + end; + + //--------------------------------------------------------------------------- + + procedure DoScrollDown(DC: TCanvas; Brush: TBrush; Area: TRect; Steps: Integer); + + begin + ScrollDC(DC.Handle, 0, Steps, Area, Area, 0, nil); + + if Step = 0 then + if not FHeader.UseColumns then + begin + DC.Brush := Brush; + DC.FillRect(Rect(Area.Left, Area.Top, Area.Right, Area.Top + Steps + 1)); + end + else + begin + Run := Rect(Area.Left, Area.Top, Area.Right, Area.Top + Steps + 1); + EraseLine; + end; + end; + + //--------------- end local functions --------------------------------------- + +begin + Result := True; + if StepSize > 0 then + begin + SecondaryStepSize := 0; + with TToggleAnimationData(Data^) do + begin + if Mode1 <> tamNoScroll then + begin + if Mode1 = tamScrollUp then + DoScrollUp(DC, Brush, R1, StepSize) + else + DoScrollDown(DC, Brush, R1, StepSize); + + if (Mode2 <> tamNoScroll) and (ScaleFactor > 0) then + begin + // As this routine is able to scroll two independent areas at once, the missing StepSize is + // computed in that case. To ensure the maximal accuracy the rounding error is accumulated. + SecondaryStepSize := Round((StepSize + MissedSteps) * ScaleFactor); + MissedSteps := MissedSteps + StepSize * ScaleFactor - SecondaryStepSize; + end; + end + else + SecondaryStepSize := StepSize; + + if Mode2 <> tamNoScroll then + if Mode2 = tamScrollUp then + DoScrollUp(DC, Brush, R2, SecondaryStepSize) + else + DoScrollDown(DC, Brush, R2, SecondaryStepSize); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMColorChange(var Message: TMessage); + +begin + if not (csLoading in ComponentState) then + begin + PrepareBitmaps(True, False); + if HandleAllocated then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMCtl3DChanged(var Message: TMessage); + +begin + inherited; + if FBorderStyle = bsSingle then + RecreateWnd; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMBiDiModeChanged(var Message: TMessage); + +begin + inherited; + + if UseRightToLeftAlignment then + FEffectiveOffsetX := FRangeX - ClientWidth + FOffsetX + else + FEffectiveOffsetX := -FOffsetX; + if FEffectiveOffsetX < 0 then + FEffectiveOffsetX := 0; + + if toAutoBidiColumnOrdering in FOptions.AutoOptions then + TVirtualTreeColumnsCracker(FHeader.Columns).ReorderColumns(UseRightToLeftAlignment); + FHeader.Invalidate(nil); +end; + +procedure TBaseVirtualTree.CMBorderChanged(var Message: TMessage); +begin + inherited; + if VclStyleEnabled and (seBorder in StyleElements) then + RecreateWnd; +end; + +procedure TBaseVirtualTree.CMParentDoubleBufferedChange(var Message: TMessage); +begin + // empty by intention, we do our own buffering +end; + +procedure TBaseVirtualTree.CMStyleChanged(var Message: TMessage); +begin + VclStyleChanged; + RecreateWnd; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMDenySubclassing(var Message: TMessage); + +// If a Windows XP Theme Manager component is used in the application it will try to subclass all controls which do not +// explicitly deny this. Virtual Treeview knows how to handle XP themes so it does not need subclassing. + +begin + Message.Result := 1; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMDrag(var Message: TCMDrag); + +var + S: TObject; + ShiftState: Integer; + P: TPoint; + Formats: TFormatArray; + Effect: Integer; + +begin + with Message, DragRec^ do + begin + S := Source; + Formats := nil; + + // Let the ancestor handle dock operations. + if S is TDragDockObject then + inherited + else + begin + // We need an extra check for the control drag object as there might be other objects not derived from this class (e.g. TActionDragObject). + // Original line of code (see issue #1295): if not (tsUserDragObject in FStates) and (S is TBaseDragControlObject) then + if (S.ClassName = TDragControlObject.ClassName) or (S.ClassName = TDragControlObjectEx.ClassName) then // see issue #1295 + S := (S as TBaseDragControlObject).Control; + case DragMessage of + dmDragEnter, dmDragLeave, dmDragMove: + begin + if DragMessage = dmDragEnter then + DoStateChange([tsVCLDragging]); + if DragMessage = dmDragLeave then + DoStateChange([tsVCLDragFinished], [tsVCLDragging]); + + if DragMessage = dmDragMove then + with ScreenToClient(Pos) do + DoAutoScroll(X, Y); + + ShiftState := 0; + // Alt key will be queried by the KeysToShiftState function in DragOver. + if GetKeyState(VK_SHIFT) < 0 then + ShiftState := ShiftState or MK_SHIFT; + if GetKeyState(VK_CONTROL) < 0 then + ShiftState := ShiftState or MK_CONTROL; + + // Allowed drop effects are simulated for VCL dd. + Effect := DROPEFFECT_MOVE or DROPEFFECT_COPY; + DragOver(S, ShiftState, TDragState(DragMessage), Pos, Effect); + FLastVCLDragTarget := FDropTargetNode; + FVCLDragEffect := Effect; + if (DragMessage = dmDragLeave) and Assigned(FDropTargetNode) then + begin + InvalidateNode(FDropTargetNode); + FDropTargetNode := nil; + end; + Result := LRESULT(Effect); + end; + dmDragDrop: + begin + ShiftState := 0; + // Alt key will be queried by the KeysToShiftState function in DragOver + if GetKeyState(VK_SHIFT) < 0 then + ShiftState := ShiftState or MK_SHIFT; + if GetKeyState(VK_CONTROL) < 0 then + ShiftState := ShiftState or MK_CONTROL; + + // allowed drop effects are simulated for VCL dd, + // replace target node with cached node from other VCL dd messages + if Assigned(FDropTargetNode) then + InvalidateNode(FDropTargetNode); + FDropTargetNode := FLastVCLDragTarget; + P := Point(Pos.X, Pos.Y); + P := ScreenToClient(P); + try + DoDragDrop(S, nil, Formats, KeysToShiftState(ShiftState), P, FVCLDragEffect, FLastDropMode); + finally + if Assigned(FDropTargetNode) then + begin + InvalidateNode(FDropTargetNode); + FDropTargetNode := nil; + end; + end; + end; + dmFindTarget: + begin + Result := LRESULT(ControlAtPos(ScreenToClient(Pos), False)); + if Result = 0 then + Result := LRESULT(Self); + + // This is a reliable place to check whether VCL drag has + // really begun. + if tsVCLDragPending in FStates then + DoStateChange([tsVCLDragging], [tsVCLDragPending, tsEditPending, tsClearPending]); + end; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMEnabledChanged(var Message: TMessage); + +begin + inherited; + + // Need to invalidate the non-client area as well, since the header must be redrawn too. + if csDesigning in ComponentState then + RedrawWindow(nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMFontChanged(var Message: TMessage); + +var + HeaderMessage: TMessage; + +begin + inherited; + AutoScale(); + + HeaderMessage.Msg := CM_PARENTFONTCHANGED; + HeaderMessage.WParam := 0; + HeaderMessage.LParam := 0; + HeaderMessage.Result := 0; + TVTHeaderCracker(FHeader).HandleMessage(HeaderMessage); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMHintShow(var Message: TCMHintShow); + +// Determines hint message (tooltip) and out-of-hint rect. +// Note: A special handling is needed here because we cannot pass wide strings back to the caller. +// I had to introduce the hint data record anyway so we can use this to pass the hint string. +// We still need to set a dummy hint string in the message to make the VCL showing the hint window. + +var + NodeRect: TRect; + SpanColumn, + Dummy, + ColLeft, + ColRight: Integer; + HitInfo: THitInfo; + ShowOwnHint: Boolean; + IsFocusedOrEditing: Boolean; + ParentForm: TCustomForm; + BottomRightCellContentMargin: TPoint; + HintKind: TVTHintKind; +begin + with Message do + begin + Result := 1; + + if PtInRect(FLastHintRect, HintInfo.CursorPos) then + Exit; + + // Determine node for which to show hint/tooltip. + with HintInfo^ do + GetHitTestInfoAt(CursorPos.X, CursorPos.Y, True, HitInfo, []); + + // Make sure a hint is only shown if the tree or at least its parent form is active. + // Active editing is ok too as long as we don't want the hint for the current edit node. + if IsEditing then + IsFocusedOrEditing := HitInfo.HitNode <> FFocusedNode + else + begin + IsFocusedOrEditing := Focused; + ParentForm := GetParentForm(Self); + if Assigned(ParentForm) then + IsFocusedOrEditing := ParentForm.Focused or Application.Active; + end; + + if (GetCapture = 0) and ShowHint and not (Dragging or IsMouseSelecting) and ([tsScrolling] * FStates = []) and + (FHeader.States = []) and IsFocusedOrEditing then + begin + with HintInfo^ do + begin + Result := 0; + ShowOwnHint := False; + + //workaround for issue #291 + //it duplicates parts of the following code and code in TVirtualTreeHintWindow + HintStr := ''; + if FHeader.UseColumns and (hoShowHint in FHeader.Options) and FHeader.InHeader(CursorPos) then + begin + CursorRect := FHeaderRect; + // Convert the cursor rectangle into real client coordinates. + OffsetRect(CursorRect, 0, -Integer(FHeader.Height)); + HitInfo.HitColumn := TVirtualTreeColumnsCracker(FHeader.Columns).GetColumnAndBounds(CursorPos, CursorRect.Left, CursorRect.Right); + if (HitInfo.HitColumn > NoColumn) and not (csLButtonDown in ControlState) and + (FHeader.Columns[HitInfo.HitColumn].Hint <> '') then + HintStr := FHeader.Columns[HitInfo.HitColumn].Hint; + end + else + if HintMode = hmDefault then + HintStr := GetShortHint(Hint) + else + if Assigned(HitInfo.HitNode) and (HitInfo.HitColumn > InvalidColumn) then + begin + if HintMode = hmToolTip then + HintStr := DoGetNodeToolTip(HitInfo.HitNode, HitInfo.HitColumn, fHintData.LineBreakStyle) + else + HintStr := DoGetNodeHint(HitInfo.HitNode, HitInfo.HitColumn, fHintData.LineBreakStyle); + end; + + // First check whether there is a header hint to show. + if FHeader.UseColumns and (hoShowHint in FHeader.Options) and FHeader.InHeader(CursorPos) then + begin + CursorRect := FHeaderRect; + // Convert the cursor rectangle into real client coordinates. + OffsetRect(CursorRect, 0, -Integer(FHeader.Height)); + HitInfo.HitColumn := TVirtualTreeColumnsCracker(FHeader.Columns).GetColumnAndBounds(CursorPos, CursorRect.Left, CursorRect.Right); + // align the vertical hint position on the bottom bound of the header, but + // avoid overlapping of mouse cursor and hint + HintPos.Y := Max(HintPos.Y, ClientToScreen(Point(0, CursorRect.Bottom)).Y); + // Note: the test for the left mouse button in ControlState might cause problems whenever the VCL does not + // realize when the button is released. This, for instance, happens when doing OLE drag'n drop and + // cancel this with ESC. + if (HitInfo.HitColumn > NoColumn) and not (csLButtonDown in ControlState) then + begin + HintStr := FHeader.Columns[HitInfo.HitColumn].Hint; + if HintStr = '' then + with FHeader.Columns[HitInfo.HitColumn] do + begin + if (2 * FMargin + CaptionWidth + 1) >= Width then + HintStr := CaptionText; + end; + if HintStr <> '' then + ShowOwnHint := True + else + Result := 1; + end + else + Result := 1; + end + else + begin + // Default mode is handled as would the tree be a usual VCL control (no own hint window necessary). + if FHintMode = hmDefault then + HintStr := GetShortHint(Hint) + else + begin + if Assigned(HitInfo.HitNode) and (HitInfo.HitColumn > InvalidColumn) then + begin + // An owner-draw tree should only display a hint when at least + // its OnGetHintSize event handler is assigned. + DoGetHintKind(HitInfo.HitNode, HitInfo.HitColumn, HintKind); + FHintData.HintRect := Rect(0, 0, 0, 0); + if (HintKind = vhkOwnerDraw) then + begin + DoGetHintSize(HitInfo.HitNode, HitInfo.HitColumn, FHintData.HintRect); + ShowOwnHint := not IsRectEmpty(FHintData.HintRect); + end + else + // For trees displaying text hints, a decision about showing the hint or not is based + // on the hint string (if it is empty then no hint is shown). + ShowOwnHint := True; + + if ShowOwnHint then + begin + if HitInfo.HitColumn > NoColumn then + begin + FHeader.Columns.GetColumnBounds(HitInfo.HitColumn, ColLeft, ColRight); + // The right column border might be extended if column spanning is enabled. + if toAutoSpanColumns in FOptions.AutoOptions then + begin + SpanColumn := HitInfo.HitColumn; + repeat + Dummy := FHeader.Columns.GetNextVisibleColumn(SpanColumn); + if (Dummy = InvalidColumn) or not ColumnIsEmpty(HitInfo.HitNode, Dummy) then + Break; + SpanColumn := Dummy; + until False; + if SpanColumn <> HitInfo.HitColumn then + FHeader.Columns.GetColumnBounds(SpanColumn, Dummy, ColRight); + end; + end + else + begin + ColLeft := 0; + ColRight := ClientWidth; + end; + + if FHintMode <> hmTooltip then + begin + // Node specific hint text. + CursorRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, False); + CursorRect.Left := ColLeft; + CursorRect.Right := ColRight; + // Align the vertical hint position on the bottom bound of the node, but + // avoid overlapping of mouse cursor and hint. + HintPos.Y := Max(HintPos.Y, ClientToScreen(CursorRect.BottomRight).Y) + ScaledPixels(2); + end + else + begin + // Tool tip to show. This means the full caption of the node must be displayed. + if vsMultiline in HitInfo.HitNode.States then + begin + if hiOnItemLabel in HitInfo.HitPositions then + begin + ShowOwnHint := True; + NodeRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, True, False); + end + else + ShowOwnHint := False; + end + else + begin + NodeRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, True, True, True); + BottomRightCellContentMargin := DoGetCellContentMargin(HitInfo.HitNode, HitInfo.HitColumn, ccmtBottomRightOnly); + + ShowOwnHint := (HitInfo.HitColumn > InvalidColumn) and PtInRect(NodeRect, CursorPos) and + (CursorPos.X <= ColRight) and (CursorPos.X >= ColLeft) and + ( + // Show hint also if the node text is partially out of the client area. + // "ColRight - 1", since the right column border is not part of this cell. + ( (NodeRect.Right + BottomRightCellContentMargin.X) > Min(ColRight - 1, ClientWidth) ) or + (NodeRect.Left < Max(ColLeft, 0)) or + ( (NodeRect.Bottom + BottomRightCellContentMargin.Y) > ClientHeight ) or + (NodeRect.Top < 0) + ); + end; + + if ShowOwnHint then + begin + // Node specific hint text given will be retrieved when needed. + HintPos := ClientToScreen(Point(NodeRect.Left, NodeRect.Top)); + CursorRect := NodeRect; + end + else + // nothing to show + Result := 1; + end; + end + else + Result := 1; // Avoid hint if this is a draw tree returning an empty hint rectangle. + end + else + begin + // No node so fall back to control's hint (if indicated) or show nothing. + if FHintMode = hmHintAndDefault then + begin + HintStr := GetShortHint(Hint); + + // Fix for the problem: Default Hint once shown stayed even when + // node hint was to be displayed. The reason was that CursorRect + // was for the full client area. Now reducing it to remove the + // columns from it. + if BidiMode = bdLeftToRight then + CursorRect.Left := Header.Columns.TotalWidth + else + CursorRect.right := CursorRect.right - Header.Columns.TotalWidth; + + if Length(HintStr) = 0 then + Result := 1 + else + ShowOwnHint := True; + end + else + Result := 1; + end; + end; + end; + + // Set our own hint window class and prepare structure to be passed to the hint window. + if ShowOwnHint and (Result = 0) then + begin + HintWindowClass := GetHintWindowClass; + FHintData.HintText := HintStr; + FHintData.Tree := Self; + FHintData.Column := HitInfo.HitColumn; + FHintData.Node := HitInfo.HitNode; + FLastHintRect := CursorRect; + HintData := @FHintData; + end + else + FLastHintRect := Rect(0, 0, 0, 0); + end; + + // Remind that a hint is about to show. + if Result = 0 then + DoStateChange([tsHint]) + else + DoStateChange([], [tsHint]); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMHintShowPause(var Message: TCMHintShowPause); + +// Tells the application that the tree (and only the tree) does not want a delayed tool tip. +// Normal hints / header hints use the default delay (except for the first time). + + begin + if ShowHint and (FHintMode = hmToolTip) then + Message.Pause^ := 0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMMouseEnter(var Message: TMessage); +begin + DoMouseEnter(); + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMMouseLeave(var Message: TMessage); + +var + LeaveStates: TVirtualTreeStates; + +begin + // Reset the last used hint rectangle in case the mouse enters the window within the bounds + if Assigned(FHintData.Tree) then + FHintData.Tree.FLastHintRect := Rect(0, 0, 0, 0); + + LeaveStates := [tsHint]; + if not (tsPanning in FStates) then + begin + StopTimer(ScrollTimer); + LeaveStates := LeaveStates + [tsScrollPending, tsScrolling]; + end; + DoStateChange([], LeaveStates); + if Assigned(FCurrentHotNode) then + begin + DoHotChange(FCurrentHotNode, nil); + if (toHotTrack in FOptions.PaintOptions) or (toCheckSupport in FOptions.MiscOptions) then + InvalidateNode(FCurrentHotNode); + FCurrentHotNode := nil; + end; + + if Assigned(Header) then + begin + with TVirtualTreeColumnsCracker(Header.Columns) do + begin + DownIndex := NoColumn; + HoverIndex := NoColumn; + CheckBoxHit := False; + end; + end; + DoMouseLeave(); + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CMMouseWheel(var Message: TCMMouseWheel); + +var + ScrollAmount: TDimension; + ScrollLines: DWORD; + RTLFactor: Integer; + WheelFactor: Double; + +begin + StopWheelPanning; + + inherited; + + if Message.Result = 0 then + begin + with Message do + begin + Result := 1; + WheelFactor := WheelDelta / WHEEL_DELTA; + if (FRangeY > ClientHeight) and (not (ssShift in ShiftState)) then + begin + // Scroll vertically if there's something to scroll... + if ssCtrl in ShiftState then + ScrollAmount := Trunc(WheelFactor * ClientHeight) + else + begin + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, @ScrollLines, 0); + if ScrollLines = WHEEL_PAGESCROLL then + ScrollAmount := Trunc(WheelFactor * ClientHeight) + else + ScrollAmount := Integer(Trunc(WheelFactor * ScrollLines * FDefaultNodeHeight)); + end; + SetOffsetY(FOffsetY + ScrollAmount); + end + else + begin + // ...else scroll horizontally if there's something to scroll. + if UseRightToLeftAlignment then + RTLFactor := -1 + else + RTLFactor := 1; + + if ssCtrl in ShiftState then + ScrollAmount := Trunc(WheelFactor * (ClientWidth - FHeader.Columns.GetVisibleFixedWidth)) + else + begin + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, @ScrollLines, 0); + if ScrollLines = WHEEL_PAGESCROLL then + ScrollAmount := Trunc(WheelFactor * (ClientWidth - FHeader.Columns.GetVisibleFixedWidth)) + else + ScrollAmount := Trunc(WheelFactor * ScrollLines * FHeader.Columns.GetScrollWidth); + end; + SetOffsetX(FOffsetX + RTLFactor * ScrollAmount); + end; + end; + + end; + +end; + +//---------------------------------------------------------------------------------------------------------------------- +procedure TBaseVirtualTree.CMSysColorChange(var Message: TMessage); + +begin + inherited; + Message.Msg := WM_SYSCOLORCHANGE; + DefaultHandler(Message); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.TVMGetItem(var Message: TMessage); + +// Screen reader support function. The method returns information about a particular node. + +const + StateMask = TVIS_STATEIMAGEMASK or TVIS_OVERLAYMASK or TVIS_EXPANDED or TVIS_DROPHILITED or TVIS_CUT or + TVIS_SELECTED or TVIS_FOCUSED; + +var + Item: PTVItemEx; + Node: PVirtualNode; + Ghosted: Boolean; + ImageIndex: TImageIndex; + R: TRect; + Text: string; +begin + // We can only return valid data if a nodes reference is given. + Item := Pointer(Message.LParam); + Message.Result := Ord(((Item.mask and TVIF_HANDLE) <> 0) and Assigned(Item.hItem)); + if Message.Result = 1 then + begin + Node := Pointer(Item.hItem); + // Child count requested? + if (Item.mask and TVIF_CHILDREN) <> 0 then + Item.cChildren := Node.ChildCount; + // Index for normal image requested? + if (Item.mask and TVIF_IMAGE) <> 0 then + begin + ImageIndex := -1; + DoGetImageIndex(Node, ikNormal, -1, Ghosted, ImageIndex); + Item.iImage := ImageIndex; + end; + // Index for selected image requested? + if (Item.mask and TVIF_SELECTEDIMAGE) <> 0 then + begin + ImageIndex := -1; + DoGetImageIndex(Node, ikSelected, -1, Ghosted, ImageIndex); + Item.iSelectedImage := ImageIndex; + end; + // State info requested? + if (Item.mask and TVIF_STATE) <> 0 then + begin + // Everything, which is possible is returned. + Item.stateMask := StateMask; + Item.state := 0; + if Node = FFocusedNode then + Item.state := Item.state or TVIS_FOCUSED; + if vsSelected in Node.States then + Item.state := Item.state or TVIS_SELECTED; + if vsCutOrCopy in Node.States then + Item.state := Item.state or TVIS_CUT; + if Node = FDropTargetNode then + Item.state := Item.state or TVIS_DROPHILITED; + if vsExpanded in Node.States then + Item.state := Item.state or TVIS_EXPANDED; + + // Construct state image and overlay image indices. They are zero based, btw. + // and -1 means there is no image. + ImageIndex := -1; + DoGetImageIndex(Node, ikState, -1, Ghosted, ImageIndex); + Item.state := Item.state or Byte(IndexToStateImageMask(ImageIndex + 1)); + ImageIndex := -1; + DoGetImageIndex(Node, ikOverlay, -1, Ghosted, ImageIndex); + Item.state := Item.state or Byte(IndexToOverlayMask(ImageIndex + 1)); + end; + // Node caption requested? + if (Item.mask and TVIF_TEXT) <> 0 then + begin + GetTextInfo(Node, -1, Font, R, Text); + + StrLCopy(Item.pszText, PWideChar(Text), Item.cchTextMax - 1); + Item.pszText[Length(Text)] := #0; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.TVMGetItemRect(var Message: TMessage); + +// Screen read support function. This method returns a node's display rectangle. + +var + TextOnly: Boolean; + Node: PVirtualNode; + +begin + // The lparam member is used two-way. On enter it contains a pointer to the item (node). + // On exit it is to be considered as pointer to a rectangle structure. + Node := Pointer(Pointer(Message.LParam)^); + Message.Result := Ord(IsVisible[Node]); + if Message.Result <> 0 then + begin + TextOnly := Message.WParam <> 0; + PRect(Message.LParam)^ := GetDisplayRect(Node, NoColumn, TextOnly); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.TVMGetNextItem(var Message: TMessage); + +// Screen read support function. This method returns a node depending on the requested case. + +var + Node: PVirtualNode; + +begin + // Start with a nil result. + Message.Result := 0; + Node := Pointer(Message.LParam); + case Message.WParam of + TVGN_CARET: + Message.Result := LRESULT(FFocusedNode); + TVGN_CHILD: + if Assigned(Node) then + Message.Result := LRESULT(GetFirstChild(Node)); + TVGN_DROPHILITE: + Message.Result := LRESULT(FDropTargetNode); + TVGN_FIRSTVISIBLE: + Message.Result := LRESULT(GetFirstVisible(nil, True)); + TVGN_LASTVISIBLE: + Message.Result := LRESULT(GetLastVisible(nil, True)); + TVGN_NEXT: + if Assigned(Node) then + Message.Result := LRESULT(GetNextSibling(Node)); + TVGN_NEXTVISIBLE: + if Assigned(Node) then + Message.Result := LRESULT(GetNextVisible(Node, True)); + TVGN_PARENT: + if Assigned(Node) and (Node <> FRoot) and (Node.Parent <> FRoot) then + Message.Result := LRESULT(Node.Parent); + TVGN_PREVIOUS: + if Assigned(Node) then + Message.Result := LRESULT(GetPreviousSibling(Node)); + TVGN_PREVIOUSVISIBLE: + if Assigned(Node) then + Message.Result := LRESULT(GetPreviousVisible(Node, True)); + TVGN_ROOT: + Message.Result := LRESULT(GetFirst); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMCancelMode(var Message: TWMCancelMode); + +begin + // Clear any transient state. + StopTimer(ExpandTimer); + StopTimer(EditTimer); + StopTimer(HeaderTimer); + StopTimer(ScrollTimer); + StopTimer(SearchTimer); + StopTimer(ThemeChangedTimer); + FSearchBuffer := ''; + FLastSearchNode := nil; + + DoStateChange([], [tsClearPending, tsEditPending, tsOLEDragPending, tsVCLDragPending, tsDrawSelecting, + tsDrawSelPending, tsIncrementalSearching]); + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMChar(var Message: TWMChar); + +begin + if tsIncrementalSearchPending in FStates then + begin + HandleIncrementalSearch(Message.CharCode); + DoStateChange([], [tsIncrementalSearchPending]); + end; + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMContextMenu(var Message: TWMContextMenu); + +// This method is called when a popup menu is about to be displayed. +// We have to cancel some pending states here to avoid interferences. + +var + HitInfo: THitInfo; + pt: TPoint; +begin + DoStateChange([], [tsClearPending, tsEditPending, tsOLEDragPending, tsVCLDragPending, tsPopupMenuShown]); + + if not Assigned(PopupMenu) then begin + // convert screen coordinates to client + pt := ScreenToClient(Point(Message.XPos, Message.YPos)); + GetHitTestInfoAt(pt.x, pt.y, True, HitInfo); // ShiftState is not used anyway here + DoPopupMenu(HitInfo.HitNode, HitInfo.HitColumn, pt); + end; + + if not (tsPopupMenuShown in FStates) then + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMCopy(var Message: TWMCopy); + +begin + CopyToClipboard; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMCut(var Message: TWMCut); + +begin + CutToClipboard; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMEnable(var Message: TWMEnable); + +begin + inherited; + RedrawWindow(nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMEraseBkgnd(var Message: TWMEraseBkgnd); + +begin + Message.Result := 1; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMGetDlgCode(var Message: TWMGetDlgCode); + +begin + Message.Result := DLGC_WANTCHARS or DLGC_WANTARROWS; + if FWantTabs then + Message.Result := Message.Result or DLGC_WANTTAB; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMHScroll(var Message: TWMHScroll); + + //--------------- local functions ------------------------------------------- + + function GetRealScrollPosition: TDimension; + + var + SI: TScrollInfo; + Bar: Integer; + + begin + SI.cbSize := SizeOf(TScrollInfo); + SI.fMask := SIF_TRACKPOS; + Bar := SB_HORZ; + GetScrollInfo(Bar, SI); + Result := SI.nTrackPos; + end; + + //--------------- end local functions --------------------------------------- + +var + RTLFactor: Integer; + +begin + if UseRightToLeftAlignment then + RTLFactor := -1 + else + RTLFactor := 1; + + case Message.ScrollCode of + SB_BOTTOM: + SetOffsetX(-FRangeX); + SB_ENDSCROLL: + begin + DoStateChange([], [tsThumbTracking]); + // avoiding to adjust the vertical scroll position while tracking makes it much smoother + // but we need to adjust the final position here then + UpdateHorizontalScrollBar(False); + end; + SB_LINELEFT: + SetOffsetX(FOffsetX + RTLFactor * FScrollBarOptions.HorizontalIncrement); + SB_LINERIGHT: + SetOffsetX(FOffsetX - RTLFactor * FScrollBarOptions.HorizontalIncrement); + SB_PAGELEFT: + SetOffsetX(FOffsetX + RTLFactor * (ClientWidth - FHeader.Columns.GetVisibleFixedWidth)); + SB_PAGERIGHT: + SetOffsetX(FOffsetX - RTLFactor * (ClientWidth - FHeader.Columns.GetVisibleFixedWidth)); + SB_THUMBPOSITION, + SB_THUMBTRACK: + begin + DoStateChange([tsThumbTracking]); + if UseRightToLeftAlignment then + SetOffsetX(-FRangeX + ClientWidth + GetRealScrollPosition) + else + SetOffsetX(-GetRealScrollPosition); + end; + SB_TOP: + SetOffsetX(0); + end; + + Message.Result := 0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMKeyDown(var Message: TWMKeyDown); + +// Keyboard event handling for node focus, selection, node specific popup menus and help invokation. +// For a detailed description of every action done here read the help. + +var + Shift: TShiftState; + Node, Temp, + LastFocused: PVirtualNode; + Offset: Integer; + ClearPending, + NeedInvalidate, + DoRangeSelect, + PerformMultiSelect: Boolean; + Context: Integer; + ParentControl: TWinControl; + R: TRect; + NewCheckState: TCheckState; + TempColumn, + NewColumn: TColumnIndex; + ActAsGrid: Boolean; + ForceSelection: Boolean; + NewWidth, + NewHeight: Integer; + RTLFactor: Integer; + + // for tabulator handling + GetStartColumn: function(ConsiderAllowFocus: Boolean = False): TColumnIndex of object; + GetNextColumn: function(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex of object; + GetNextNode: TGetNextNodeProc; + + KeyState: TKeyboardState; + Buffer: array[0..1] of AnsiChar; + + //--------------- local functions ------------------------------------------- + function getPreviousVisibleAutoSpanColumn(acolumn: TColumnIndex; anode: PVirtualNode): TColumnIndex; + var + PrevColumn: Integer; + begin + if (not assigned(anode)) + or (not FHeader.UseColumns) + or (not (toAutoSpanColumns in FOptions.AutoOptions)) + or (acolumn = FHeader.MainColumn) then + begin + //previously existing logic + result := FHeader.Columns.GetPreviousVisibleColumn(acolumn, True); + exit; + end; + //consider auto spanning + with FHeader.Columns do //standard loop for auto span + begin + PrevColumn := acolumn; + repeat + result := FHeader.Columns.GetPreviousVisibleColumn(PrevColumn); + if (result = InvalidColumn) or + (not ColumnIsEmpty(anode, result)) + //Any other BidiMode is not supported as already + //documented by original developer + or (Items[result].BidiMode <> bdLeftToRight) then + Break; + PrevColumn := result; + until False; + end; + end; + + //--------------------------------------------------------------------------- + function getNextVisibleAutoSpanColumn(acolumn: TColumnIndex; anode: PVirtualNode): TColumnIndex; + var + NextColumn: Integer; + begin + if (not assigned(anode)) + or (not FHeader.UseColumns) + or (not (toAutoSpanColumns in FOptions.AutoOptions)) + or (acolumn = FHeader.MainColumn) then + begin + //previously existing logic + result := FHeader.Columns.GetNextVisibleColumn(acolumn, True); + exit; + end; + //consider auto spanning + with FHeader.Columns do //standard loop for auto span + begin + NextColumn := acolumn; + repeat + result := FHeader.Columns.GetNextVisibleColumn(NextColumn); + if (result = InvalidColumn) or + not ColumnIsEmpty(anode, result) + //Any other BidiMode is not supported as already + //documented by original developer + or (Items[result].BidiMode <> bdLeftToRight) then + Break; + NextColumn := result; + until False; + end; + end; + + //--------------------------------------------------------------------------- + function isEmptyAutoSpanColumn(acolumn: TColumnIndex; anode: PVirtualNode): boolean; + var + previousColumn: Integer; + begin + result := false; + if (not assigned(anode)) + or (not FHeader.UseColumns) + or (not (toAutoSpanColumns in FOptions.AutoOptions)) + or (acolumn = FHeader.MainColumn) then + exit; + with FHeader.Columns do + begin + previousColumn := FHeader.Columns.GetPreviousVisibleColumn(acolumn); + if (previousColumn = InvalidColumn) //there is no previous column + //Any other BidiMode is not supported as already + //documented by original developer + or (Items[acolumn].BidiMode <> bdLeftToRight) then + exit; //returning false + result := ColumnIsEmpty(anode, acolumn); + end; + end; + + + //--------------- end local functions --------------------------------------- + +begin + // Make form key preview work and let application modify the key if it wants this. + inherited; + + with Message do + begin + Shift := KeyDataToShiftState(KeyData); + // Ask the application if the default key handling is desired. + if DoKeyAction(CharCode, Shift) then + begin + if (CharCode in [VK_HOME, VK_END, VK_PRIOR, VK_NEXT, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_BACK, VK_TAB]) and (RootNode.FirstChild <> nil) then + begin + PerformMultiSelect := (ssShift in Shift) and (toMultiSelect in FOptions.SelectionOptions) and not IsEditing; + + // Flag to avoid range selection in case of single node advance. + DoRangeSelect := (CharCode in [VK_HOME, VK_END, VK_PRIOR, VK_NEXT]) and PerformMultiSelect and not IsEditing; + + NeedInvalidate := DoRangeSelect or (FSelectionCount > 1); + ActAsGrid := toGridExtensions in FOptions.MiscOptions; + ClearPending := (Shift = []) or (ActAsGrid and not (ssShift in Shift)) or + not (toMultiSelect in FOptions.SelectionOptions) or (CharCode in [VK_TAB, VK_BACK]); + + // Keep old focused node for range selection. Use a default node if none was focused until now. + LastFocused := FFocusedNode; + if (LastFocused = nil) and (Shift <> []) then + LastFocused := GetFirstVisible(nil, True); + + // Set an initial range anchor if there is not yet one. + if FRangeAnchor = nil then + FRangeAnchor := GetFirstSelected; + if FRangeAnchor = nil then + FRangeAnchor := GetFirst; + + if UseRightToLeftAlignment then + RTLFactor := -1 + else + RTLFactor := 1; + + // Determine new focused node. + case CharCode of + VK_HOME, VK_END: + begin + if (CharCode = VK_END) xor UseRightToLeftAlignment then + begin + GetStartColumn := FHeader.Columns.GetLastVisibleColumn; + GetNextColumn := FHeader.Columns.GetPreviousVisibleColumn; + GetNextNode := GetPreviousVisible; + Node := GetLastVisible(nil, True); + end + else + begin + GetStartColumn := FHeader.Columns.GetFirstVisibleColumn; + GetNextColumn := FHeader.Columns.GetNextVisibleColumn; + GetNextNode := GetNextVisible; + Node := GetFirstVisible(nil, True); + end; + + // Advance to next/previous visible column. + if FHeader.UseColumns then + NewColumn := GetStartColumn + else + NewColumn := NoColumn; + // Find a column for the new/current node which can be focused. + // Make the 'DoFocusChanging' for finding a valid column + // identifiable from the 'DoFocusChanging' raised later on by + // "FocusedNode := Node;" + while (NewColumn > NoColumn) and not DoFocusChanging(FFocusedNode, FFocusedNode, FFocusedColumn, NewColumn) do + NewColumn := GetNextColumn(NewColumn); + if NewColumn > InvalidColumn then + begin + if (Shift = [ssCtrl]) and not ActAsGrid then + begin + ScrollIntoView(Node, toCenterScrollIntoView in FOptions.SelectionOptions, + not (toDisableAutoscrollOnFocus in FOptions.AutoOptions)); + if (CharCode = VK_HOME) and not UseRightToLeftAlignment then + SetOffsetX(0) + else + SetOffsetX(-MaxInt); + end + else + begin + if not ActAsGrid or (ssCtrl in Shift) then + FocusedNode := Node; + //fix: In grid mode, if full row select option is ON, + //then also go to the node determined from the earlier logic + if ActAsGrid and (toFullRowSelect in FOptions.SelectionOptions) then + FocusedNode := Node; + if ActAsGrid and not (toFullRowSelect in FOptions.SelectionOptions) then + begin + FocusedColumn := NewColumn; + // fix: If auto span is ON the last column may be a merged column. So take + // care of selecting the whole merged column on END key. + if (CharCode = VK_END) and isEmptyAutoSpanColumn(NewColumn, FFocusedNode) then + FocusedColumn := getPreviousVisibleAutoSpanColumn(NewColumn, FFocusedNode); + end; + end; + end; + end; + VK_PRIOR: + if Shift = [ssCtrl, ssShift] then + SetOffsetX(FOffsetX + ClientWidth) + else + if [ssShift, ssAlt] = Shift then + begin + if FFocusedColumn <= NoColumn then + NewColumn := FHeader.Columns.GetFirstVisibleColumn + else + begin + Offset := FHeader.Columns.GetVisibleFixedWidth; + NewColumn := FFocusedColumn; + while True do + begin + TempColumn := FHeader.Columns.GetPreviousVisibleColumn(NewColumn); + NewWidth := FHeader.Columns[NewColumn].Width; + if (TempColumn <= NoColumn) or + (Offset + NewWidth >= ClientWidth) or + (coFixed in FHeader.Columns[TempColumn].Options) then + Break; + NewColumn := TempColumn; + Inc(Offset, NewWidth); + end; + end; + SetFocusedColumn(NewColumn); + end + else + if ssCtrl in Shift then + SetOffsetY(FOffsetY + ClientHeight) + else + begin + Offset := 0; + // If there's no focused node then just take the very first visible one. + if FFocusedNode = nil then + Node := GetFirstVisible(nil, True) + else + begin + // Go up as many nodes as comprise together a size of ClientHeight. + Node := FFocusedNode; + while True do + begin + Temp := GetPreviousVisible(Node, True); + NewHeight := NodeHeight[Node]; + if (Temp = nil) or (Offset + NewHeight >= ClientHeight) then + Break; + Node := Temp; + Inc(Offset, NodeHeight[Node]); + end; + end; + FocusedNode := Node; + end; + VK_NEXT: + if Shift = [ssCtrl, ssShift] then + SetOffsetX(FOffsetX - ClientWidth) + else + if [ssShift, ssAlt] = Shift then + begin + if FFocusedColumn <= NoColumn then + NewColumn := FHeader.Columns.GetFirstVisibleColumn + else + begin + Offset := FHeader.Columns.GetVisibleFixedWidth; + NewColumn := FFocusedColumn; + while True do + begin + TempColumn := FHeader.Columns.GetNextVisibleColumn(NewColumn); + NewWidth := FHeader.Columns[NewColumn].Width; + if (TempColumn <= NoColumn) or + (Offset + NewWidth >= ClientWidth) or + (coFixed in FHeader.Columns[TempColumn].Options) then + Break; + NewColumn := TempColumn; + Inc(Offset, NewWidth); + end; + end; + SetFocusedColumn(NewColumn); + end + else + if ssCtrl in Shift then + SetOffsetY(FOffsetY - ClientHeight) + else + begin + Offset := 0; + // If there's no focused node then just take the very last one. + if FFocusedNode = nil then + Node := GetLastVisible(nil, True) + else + begin + // Go up as many nodes as comprise together a size of ClientHeight. + Node := FFocusedNode; + while True do + begin + Temp := GetNextVisible(Node, True); + NewHeight := NodeHeight[Node]; + if (Temp = nil) or (Offset + NewHeight >= ClientHeight) then + Break; + Node := Temp; + Inc(Offset, NewHeight); + end; + end; + FocusedNode := Node; + end; + VK_UP: + begin + // scrolling without selection change + if ssCtrl in Shift then + SetOffsetY(FOffsetY + FDefaultNodeHeight) + else + begin + if FFocusedNode = nil then + Node := GetLastVisible(nil, True) + else + Node := GetPreviousVisible(FFocusedNode, True); + + if Assigned(Node) then + begin + if not EndEditNode then + exit; + if (not PerformMultiSelect or (CompareNodePositions(LastFocused, Node) < -1)) and Assigned(FFocusedNode) then + ClearSelection(False); // Clear selection only if more than one node was skipped. See issue #926 + if FFocusedColumn <= NoColumn then + FFocusedColumn := FHeader.MainColumn; + FocusedNode := Node; + end + else + if Assigned(FFocusedNode) then + InvalidateNode(FFocusedNode); + end; + end; + VK_DOWN: + begin + // scrolling without selection change + if ssCtrl in Shift then + SetOffsetY(FOffsetY - FDefaultNodeHeight) + else + begin + if FFocusedNode = nil then + Node := GetFirstVisible(nil, True) + else + Node := GetNextVisible(FFocusedNode, True); + + if Assigned(Node) then + begin + if not EndEditNode then + exit; + if (not PerformMultiSelect or (CompareNodePositions(LastFocused, Node) > 1)) and Assigned(FFocusedNode) then + ClearSelection(False); // Clear selection only if more than one node was skipped. See issue #926 + if FFocusedColumn <= NoColumn then + FFocusedColumn := FHeader.MainColumn; + FocusedNode := Node; + end + else + if Assigned(FFocusedNode) then + InvalidateNode(FFocusedNode); + end; + end; + VK_LEFT: + begin + // special handling + if ssCtrl in Shift then + SetOffsetX(FOffsetX + RTLFactor * FHeader.Columns.GetScrollWidth) + else + begin + // other special cases + Context := NoColumn; + if (toExtendedFocus in FOptions.SelectionOptions) and (toGridExtensions in FOptions.MiscOptions) then + begin + Context := getPreviousVisibleAutoSpanColumn(FFocusedColumn, FFocusedNode); + if Context > NoColumn then + FocusedColumn := Context; + end + else + if Assigned(FFocusedNode) and (vsExpanded in FFocusedNode.States) and + (Shift = []) and (vsHasChildren in FFocusedNode.States) then + ToggleNode(FFocusedNode) + else + begin + if FFocusedNode = nil then + FocusedNode := GetFirstVisible(nil, True) + else + begin + if FFocusedNode.Parent <> FRoot then + Node := FFocusedNode.Parent + else + Node := nil; + if Assigned(Node) then + begin + if PerformMultiSelect then + begin + // and a third special case + if FFocusedNode.Index > 0 then + DoRangeSelect := True + else + if CompareNodePositions(Node, FRangeAnchor) > 0 then + RemoveFromSelection(FFocusedNode); + end; + FocusedNode := Node; + end + else begin + // If already a root node is selected, then scroll to the left as there is nothing else we could do. #691 + SetOffsetX(FOffsetX + RTLFactor * FHeader.Columns.GetScrollWidth); + end;//else + end; + end; + end; + end; + VK_RIGHT: + begin + // special handling + if ssCtrl in Shift then + SetOffsetX(FOffsetX - RTLFactor * FHeader.Columns.GetScrollWidth) + else + begin + // other special cases + Context := NoColumn; + if (toExtendedFocus in FOptions.SelectionOptions) and (toGridExtensions in FOptions.MiscOptions) then + begin + Context := getNextVisibleAutoSpanColumn(FFocusedColumn, FFocusedNode); + if Context > NoColumn then + FocusedColumn := Context; + end + else + if Assigned(FFocusedNode) and not (vsExpanded in FFocusedNode.States) and + (Shift = []) and (vsHasChildren in FFocusedNode.States) then + ToggleNode(FFocusedNode) + else + begin + if FFocusedNode = nil then + FocusedNode := GetFirstVisible(nil, True) + else + begin + Node := GetFirstVisibleChild(FFocusedNode); + if Assigned(Node) then + begin + if PerformMultiSelect and (CompareNodePositions(Node, FRangeAnchor) < 0) then + RemoveFromSelection(FFocusedNode); + FocusedNode := Node; + end + else begin + // If already a leaf node is selected, then scroll to the right as there is nothing else we could do. #691 + SetOffsetX(FOffsetX - RTLFactor * FHeader.Columns.GetScrollWidth); + end;//else + end; + end; + end; + end; + VK_BACK: + if tsIncrementalSearching in FStates then + DoStateChange([tsIncrementalSearchPending]) + else + if Assigned(FFocusedNode) and (FFocusedNode.Parent <> FRoot) then + FocusedNode := FocusedNode.Parent; + VK_TAB: + if (toExtendedFocus in FOptions.SelectionOptions) and FHeader.UseColumns then + begin + // In order to avoid duplicating source code just to change the direction + // we use function variables. + if ssShift in Shift then + begin + GetStartColumn := FHeader.Columns.GetLastVisibleColumn; + GetNextColumn := FHeader.Columns.GetPreviousVisibleColumn; + GetNextNode := GetPreviousVisible; + end + else + begin + GetStartColumn := FHeader.Columns.GetFirstVisibleColumn; + GetNextColumn := FHeader.Columns.GetNextVisibleColumn; + GetNextNode := GetNextVisible; + end; + + // Advance to next/previous visible column/node. + Node := FFocusedNode; + NewColumn := GetNextColumn(FFocusedColumn, True); + repeat + // Find a column for the current node which can be focused. + while (NewColumn > NoColumn) and not DoFocusChanging(FFocusedNode, Node, FFocusedColumn, NewColumn) + //Fix: for Tab Key to properly skip the empty auto span column + or isEmptyAutoSpanColumn(NewColumn, Node) do + NewColumn := GetNextColumn(NewColumn, True); + + if NewColumn > NoColumn then + begin + // Set new node and column in one go. + SetFocusedNodeAndColumn(Node, NewColumn); + Break; + end; + + // No next column was accepted for the current node. So advance to next node and try again. + Node := GetNextNode(Node); + NewColumn := GetStartColumn; + + // fix: From last column, the Tab key should always go to next row irrespective of auto span + // Similarly the Shift-Tab key should go to previos row from first column + if (Node <> nil) and (NewColumn > NoColumn) then + SetFocusedNodeAndColumn(Node, NewColumn); + + until Node = nil; + end; + end; + + // Clear old selection if required but take care to select the new focused node if it was not selected before. + ForceSelection := False; + if ClearPending and ((LastFocused <> FFocusedNode) or (FSelectionCount <> 1)) then + begin + ClearSelection(not Assigned(FFocusedNode)); + ForceSelection := True; + end; + + // Determine new selection anchor. + if Shift = [] then + begin + FRangeAnchor := FFocusedNode; + FLastSelectionLevel := GetNodeLevelForSelectConstraint(FFocusedNode); + end; + + if Assigned(FFocusedNode) then + begin + // Finally change the selection for a specific range of nodes. + if DoRangeSelect then + ToggleSelection(LastFocused, FFocusedNode) + // Make sure the new focused node is also selected. + else if (LastFocused <> FFocusedNode) then begin + if ForceSelection then + AddToSelection(FFocusedNode, False) + else + ToggleSelection(LastFocused, FFocusedNode); // See issue #926 + end; + end; + + // If a repaint is needed then paint the entire tree because of the ClearSelection call, + if NeedInvalidate then + Invalidate; + end + else + begin + // Second chance for keys not directly concerned with selection changes. + + // For +, -, /, * keys on the main keyboard (not numpad) there is no virtual key code defined. + // We have to do special processing to get them working too. + GetKeyboardState(KeyState); + // Avoid conversion to control characters. We have captured the control key state already in Shift. + KeyState[VK_CONTROL] := 0; + if ToASCII(Message.CharCode, (Message.KeyData shr 16) and 7, KeyState, PChar(@Buffer), 0) > 0 then + begin + case Buffer[0] of + '*': + CharCode := VK_MULTIPLY; + '+': + CharCode := VK_ADD; + '/': + CharCode := VK_DIVIDE; + '-': + CharCode := VK_SUBTRACT; + end; + end; + + // According to https://web.archive.org/web/20041129085958/http://www.it-faq.pl/mskb/99/337.HTM + // there is a problem with ToASCII when used in conjunction with dead chars. + // The article recommends to call ToASCII twice to restore a deleted flag in the key message + // structure under certain circumstances. It turned out it is best to always call ToASCII twice. + ToASCII(Message.CharCode, (Message.KeyData shr 16) and 7, KeyState, PChar(@Buffer), 0); + + case CharCode of + VK_F2: + if (Shift = []) and Assigned(FFocusedNode) and CanEdit(FFocusedNode, FFocusedColumn) then + begin + FEditColumn := FFocusedColumn; + DoEdit; + end; + VK_ADD: + if not (tsIncrementalSearching in FStates) then + begin + if ssCtrl in Shift then begin// When changing this code review issue #781 + if ((toReverseFullExpandHotKey in TreeOptions.MiscOptions) and (Shift = [ssCtrl])) xor (Shift = [ssCtrl, ssShift]) then + FullExpand + else if Shift = [ssCtrl] then + FHeader.AutoFitColumns + end + else if Shift = [] then begin + if Assigned(FFocusedNode) and not (vsExpanded in FFocusedNode.States) then + ToggleNode(FFocusedNode); + end// if Shift = [] + else + DoStateChange([tsIncrementalSearchPending]); + end;//if not (tsIncrementalSearching in FStates) + VK_SUBTRACT: + if not (tsIncrementalSearching in FStates) then + begin + if ssCtrl in Shift then + if (toReverseFullExpandHotKey in TreeOptions.MiscOptions) xor (ssShift in Shift) then + FullCollapse + else + FHeader.RestoreColumns + else + if Assigned(FFocusedNode) and (vsExpanded in FFocusedNode.States) then + ToggleNode(FFocusedNode); + end + else + DoStateChange([tsIncrementalSearchPending]); + VK_MULTIPLY: + if not (tsIncrementalSearching in FStates) then + begin + if Assigned(FFocusedNode) then + FullExpand(FFocusedNode); + end + else + DoStateChange([tsIncrementalSearchPending]); + VK_DIVIDE: + if not (tsIncrementalSearching in FStates) then + begin + if Assigned(FFocusedNode) then + FullCollapse(FFocusedNode); + end + else + DoStateChange([tsIncrementalSearchPending]); + VK_ESCAPE: // cancel actions currently in progress + begin + if IsMouseSelecting then + begin + DoStateChange([], [tsDrawSelecting, tsDrawSelPending]); + Invalidate; + end + else + if IsEditing then + CancelEditNode; + end; + VK_SPACE: + if (toCheckSupport in FOptions.MiscOptions) and Assigned(FFocusedNode) and + (FFocusedNode.CheckType <> ctNone) then + begin + NewCheckState := DetermineNextCheckState(FFocusedNode.CheckType, GetCheckState(FFocusedNode)); + if DoChecking(FFocusedNode, NewCheckState) then + begin + if SelectedCount > 1 then + SetCheckStateForAll(NewCheckState, True) + else + DoCheckClick(FFocusedNode, NewCheckState); + end; + end + else + DoStateChange([tsIncrementalSearchPending]); + VK_F1: + if Assigned(FOnGetHelpContext) then + begin + Context := 0; + if Assigned(FFocusedNode) then + begin + Node := FFocusedNode; + // Traverse the tree structure up to the root. + repeat + FOnGetHelpContext(Self, Node, IfThen(FFocusedColumn > NoColumn, FFocusedColumn, 0), Context); + Node := Node.Parent; + until (Node = FRoot) or (Context <> 0); + end; + + // If no help context could be found try the tree's one or its parent's contexts. + ParentControl := Self; + while Assigned(ParentControl) and (Context = 0) do + begin + Context := ParentControl.HelpContext; + ParentControl := ParentControl.Parent; + end; + if Context <> 0 then + Application.HelpContext(Context); + end; + VK_APPS: + if Assigned(FFocusedNode) then + begin + R := GetDisplayRect(FFocusedNode, FFocusedColumn, True); + Offset := DoGetNodeWidth(FFocusedNode, FFocusedColumn); + if FFocusedColumn >= 0 then + begin + if Offset > FHeader.Columns[FFocusedColumn].Width then + Offset := FHeader.Columns[FFocusedColumn].Width; + end + else + begin + if Offset > ClientWidth then + Offset := ClientWidth; + end; + DoPopupMenu(FFocusedNode, FFocusedColumn, Point(R.Left + Offset div 2, (R.Top + R.Bottom) div 2)); + end + else + DoPopupMenu(nil, FFocusedColumn, Point(-1, -1)); + Ord('a'), Ord('A'): + if ssCtrl in Shift then + SelectAll(True) + else + DoStateChange([tsIncrementalSearchPending]); + else + begin + // Use the key for incremental search. + // Since we are dealing with Unicode all the time there should be a more sophisticated way + // of checking for valid characters for incremental search. + // This is available but would require to include a significant amount of Unicode character + // properties, so we stick with the simple space check. + if ((Shift * [ssCtrl, ssAlt] = []) or ((Shift * [ssCtrl, ssAlt] = [ssCtrl, ssAlt]))) and (CharCode >= 32) then + DoStateChange([tsIncrementalSearchPending]); + end; + end; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMKeyUp(var Message: TWMKeyUp); + +begin + inherited; + + case Message.CharCode of + VK_TAB: + EnsureNodeFocused(); // Always select a node if the control gets the focus via TAB key, #237 + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMKillFocus(var Msg: TWMKillFocus); + +var + Form: TCustomForm; + Control: TWinControl; + Pos: TSmallPoint; + Unknown: IUnknown; + +begin + inherited; + + // Remove hint if shown currently. + if tsHint in Self.FStates then + Application.CancelHint; + + // Stop wheel panning if active. + StopWheelPanning; + + // Don't let any timer continue if the tree is no longer the active control (except change timers). + StopTimer(ExpandTimer); + StopTimer(EditTimer); + StopTimer(HeaderTimer); + StopTimer(ScrollTimer); + StopTimer(SearchTimer); + FSearchBuffer := ''; + FLastSearchNode := nil; + + DoStateChange([], [tsScrollPending, tsScrolling, tsEditPending, tsLeftButtonDown, tsRightButtonDown, + tsMiddleButtonDown, tsOLEDragPending, tsVCLDragPending, tsIncrementalSearching, tsNodeHeightTrackPending, + tsNodeHeightTracking]); + + if (FSelectionCount > 0) or not (toGhostedIfUnfocused in FOptions.PaintOptions) then + Invalidate + else + if Assigned(FFocusedNode) then + InvalidateNode(FFocusedNode); + + // Workaround for wrapped non-VCL controls (like TWebBrowser), which do not use VCL mechanisms and + // leave the ActiveControl property in the wrong state, which causes trouble when the control is refocused. + Form := GetParentForm(Self); + if Assigned(Form) and (Form.ActiveControl = Self) then + begin + Cardinal(Pos) := GetMessagePos; + Control := FindVCLWindow(SmallPointToPoint(Pos)); + // Every control derived from TOleControl has potentially the focus problem. In order to avoid including + // the OleCtrls unit (which will, among others, include Variants), which would allow to test for the TOleControl + // class, the IOleClientSite interface is used for the test, which is supported by TOleControl and a good indicator. + if Assigned(Control) and Control.GetInterface(IOleClientSite, Unknown) then + Form.ActiveControl := nil; + + // For other classes the active control should not be modified. Otherwise you need two clicks to select it. + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMLButtonDblClk(var Message: TWMLButtonDblClk); + +var + HitInfo: THitInfo; + +begin + DoStateChange([tsLeftDblClick]); + try + // get information about the hit, before calling inherited, is this may change the scroll postion and so the node under the mouse would chnage and would no longer be the one the user actually clicked + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + HandleMouseDblClick(Message, HitInfo); + // Call inherited after doing our standard handling, as the event handler may close the form or re-fill the control, so our clicked node would be no longer valid. + // Our standard handling does not do that. + inherited; + // #909 + // if we show a modal form in the HandleMouseDblClick(), the mouse capture wont be released + if csCaptureMouse in ControlStyle then MouseCapture := False; + finally + DoStateChange([], [tsLeftDblClick]); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMLButtonDown(var Message: TWMLButtonDown); + +var + HitInfo: THitInfo; + +begin + DoStateChange([tsLeftButtonDown]); + inherited; + + // get information about the hit + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + HandleMouseDown(Message, HitInfo); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMLButtonUp(var Message: TWMLButtonUp); + +var + HitInfo: THitInfo; + +begin + DoStateChange([], [tsLeftButtonDown, tsNodeHeightTracking, tsNodeHeightTrackPending]); + + // get information about the hit + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + HandleMouseUp(Message, HitInfo); + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMMButtonDblClk(var Message: TWMMButtonDblClk); + +var + HitInfo: THitInfo; + +begin + DoStateChange([tsMiddleDblClick]); + inherited; + + // get information about the hit + if toMiddleClickSelect in FOptions.SelectionOptions then + begin + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + HandleMouseDblClick(Message, HitInfo); + end; + DoStateChange([], [tsMiddleDblClick]); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMMButtonDown(var Message: TWMMButtonDown); + +var + HitInfo: THitInfo; + +begin + DoStateChange([tsMiddleButtonDown]); + + if FHeader.States = [] then + begin + inherited; + + // Start wheel panning or scrolling if not already active, allowed and scrolling is useful at all. + if (toWheelPanning in FOptions.MiscOptions) and not (tsPanning in FStates) and + ((FRangeX > ClientWidth) or (FRangeY > ClientHeight)) then + begin + FLastClickPos := SmallPointToPoint(Message.Pos); + StartWheelPanning(FLastClickPos); + end + else + begin + StopWheelPanning; + + // Get information about the hit. + if toMiddleClickSelect in FOptions.SelectionOptions then + begin + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + HandleMouseDown(Message, HitInfo); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMMButtonUp(var Message: TWMMButtonUp); + +var + HitInfo: THitInfo; + +begin + DoStateChange([], [tsMiddleButtonDown]); + + if not (tsPanning in FStates) then + if FHeader.States = [] then + begin + inherited; + + // get information about the hit + if toMiddleClickSelect in FOptions.SelectionOptions then + begin + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + HandleMouseUp(Message, HitInfo); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMNCCalcSize(var Message: TWMNCCalcSize); + +begin + inherited; + + with FHeader do + if hoVisible in FHeader.Options then + with Message.CalcSize_Params^ do + Inc(rgrc[0].Top, Height); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMNCDestroy(var Message: TWMNCDestroy); + +// Used to release a reference of the drag manager. This is the only reliable way we get notified about +// window destruction, because of the automatic release of a window if its parent window is freed. + +begin + InterruptValidation; + + StopTimer(ChangeTimer); + StopTimer(StructureChangeTimer); + + if not (csDesigning in ComponentState) and HandleAllocated then + RevokeDragDrop(Handle); + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMNCHitTest(var Message: TWMNCHitTest); + +begin + inherited; + if (hoVisible in FHeader.Options) and + FHeader.InHeader(ScreenToClient(SmallPointToPoint(Message.Pos))) then + Message.Result := HTBORDER; +end; + +//---------------------------------------------------------------------------------------------------------------------- + + +procedure TBaseVirtualTree.WMNCPaint(var Message: TWMNCPaint); + +var + DC: HDC; + R: TRect; + Flags: DWORD; + ExStyle: Integer; + TempRgn: HRGN; + BorderWidth, + BorderHeight: Integer; + +begin + if tsUseThemes in FStates then + begin + // If theming is enabled and the client edge border is set for the window then prevent the default window proc + // from painting the old border to avoid flickering. + ExStyle := GetWindowLong(Handle, GWL_EXSTYLE); + if (ExStyle and WS_EX_CLIENTEDGE) <> 0 then + begin + GetWindowRect(Handle, R); + // Determine width of the client edge. + BorderWidth := GetSystemMetrics(SM_CXEDGE); + BorderHeight := GetSystemMetrics(SM_CYEDGE); + InflateRect(R, -BorderWidth, -BorderHeight); + TempRgn := CreateRectRgnIndirect(R); + // Exclude the border from the message region if there is one. Otherwise just use the inflated + // window area region. + if Message.Rgn <> 1 then + CombineRgn(TempRgn, Message.Rgn, TempRgn, RGN_AND); + DefWindowProc(Handle, Message.Msg, WPARAM(TempRgn), 0); + DeleteObject(TempRgn); + end + else + DefaultHandler(Message); + end + else + DefaultHandler(Message); + + Flags := DCX_CACHE or DCX_CLIPSIBLINGS or DCX_WINDOW or DCX_VALIDATE; + + if (Message.Rgn = 1) then + DC := GetDCEx(Handle, 0, Flags) + else + DC := GetDCEx(Handle, Message.Rgn, Flags or DCX_INTERSECTRGN); + + if DC <> 0 then + try + OriginalWMNCPaint(DC); + finally + ReleaseDC(Handle, DC); + end; + if (((tsUseThemes in FStates) and not VclStyleEnabled) or (VclStyleEnabled and (seBorder in StyleElements))) then + StyleServices.PaintBorder(Self, False) + else + if (VclStyleEnabled and not (seBorder in StyleElements)) then + TStyleManager.SystemStyle.PaintBorder(Self, False) +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMPaint(var Message: TWMPaint); +var + DC: HDC; +begin + if tsVCLDragging in FStates then + ImageList_DragShowNolock(False); + if csPaintCopy in ControlState then + FUpdateRect := ClientRect + else + GetUpdateRect(Handle, FUpdateRect, True); + + inherited; + + if tsVCLDragging in FStates then + ImageList_DragShowNolock(True); + + if hoVisible in FHeader.Options then + begin + DC := GetDCEx(Handle, 0, DCX_CACHE or DCX_CLIPSIBLINGS or DCX_WINDOW or DCX_VALIDATE); + if DC <> 0 then + try + FHeader.Columns.PaintHeader(DC, FHeaderRect, -FEffectiveOffsetX); + finally + ReleaseDC(Handle, DC); + end; + end;//if header visible +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMPaste(var Message: TWMPaste); + +begin + PasteFromClipboard; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMPrint(var Message: TWMPrint); + +// This message is sent to request that the tree draws itself to a given device context. This includes not only +// the client area but also the non-client area (header!). + +begin + // Draw only if the window is visible or visibility is not required. + if ((Message.Flags and PRF_CHECKVISIBLE) = 0) or IsWindowVisible(Handle) then + Header.Columns.PaintHeader(Message.DC, FHeaderRect, -FEffectiveOffsetX); + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMRButtonDblClk(var Message: TWMRButtonDblClk); + +var + HitInfo: THitInfo; + +begin + DoStateChange([tsRightDblClick]); + inherited; + + // get information about the hit + if toMiddleClickSelect in FOptions.SelectionOptions then + begin + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + HandleMouseDblClick(Message, HitInfo); + end; + DoStateChange([], [tsRightDblClick]); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMRButtonDown(var Message: TWMRButtonDown); + +var + HitInfo: THitInfo; + RemoveSynchMode: Boolean; // Needed to restore tsSynchMode correctly + +begin + DoStateChange([tsRightButtonDown]); + + if FHeader.States = [] then + begin + inherited; + + // get information about the hit + if toRightClickSelect in FOptions.SelectionOptions then + begin + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + // Go temporarily into sync mode to avoid a delayed change event for the node when selecting. #679 + RemoveSynchMode := not (tsSynchMode in FStates); + Include(FStates, tsSynchMode); + HandleMouseDown(Message, HitInfo); + if RemoveSynchMode then + Exclude(FStates, tsSynchMode); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMRButtonUp(var Message: TWMRButtonUp); + +// handle right click selection and node specific popup menu + +var + HitInfo: THitInfo; + +begin + DoStateChange([], [tsRightButtonDown]); + + if FHeader.States = [] then + begin + Application.CancelHint; + + if IsMouseSelecting and Assigned(PopupMenu) then + begin + // Reset selection state already here, before the inherited handler opens the default menu. + DoStateChange([], [tsDrawSelecting, tsDrawSelPending]); + Invalidate; + end; + + inherited; + + // get information about the hit + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + + if toRightClickSelect in FOptions.SelectionOptions then + HandleMouseUp(Message, HitInfo); + + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMSetCursor(var Message: TWMSetCursor); + +// Sets the hot node mouse cursor for the tree. Cursor changes for the header are handled in Header.HandleMessage. + +var + NewCursor: TCursor; + HitInfo: THitInfo; + P: TPoint; + Node: PVirtualNode; + +begin + with Message do + begin + // Feature: design-time header #415 + // Allow header to handle cursor and return control's default if it did nothing + if (CursorWnd = Handle) and not (tsPanning in FStates) then + begin + if not TVTHeaderCracker(FHeader).HandleMessage(TMessage(Message)) then + begin + // Apply own cursors only if there is no global cursor set. + if Screen.Cursor = crDefault then + begin + // node resizing and hot tracking - for run-time only + if not (csDesigning in ComponentState) then + begin + NewCursor := crDefault; + if (toNodeHeightResize in FOptions.MiscOptions) then + begin + GetCursorPos(P); + P := ScreenToClient(P); + GetHitTestInfoAt(P.X, P.Y, True, HitInfo, []); + if (hiOnItem in HitInfo.HitPositions) and + ([hiUpperSplitter, hiLowerSplitter] * HitInfo.HitPositions <> []) then + begin + if hiUpperSplitter in HitInfo.HitPositions then + Node := GetPreviousVisible(HitInfo.HitNode, True) + else + Node := HitInfo.HitNode; + + if CanSplitterResizeNode(P, Node, HitInfo.HitColumn) then + NewCursor := crVSplit; + end; + end; + + if (NewCursor = crDefault) then + if (toHotTrack in FOptions.PaintOptions) and Assigned(FCurrentHotNode) and (FHotCursor <> crDefault) then + NewCursor := FHotCursor + else + NewCursor := Cursor; + + DoGetCursor(NewCursor); + end + else + NewCursor := Cursor; + Winapi.Windows.SetCursor(Screen.Cursors[NewCursor]); + Message.Result := 1; + end + else + inherited; + end; + end + else + inherited; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMSetFocus(var Msg: TWMSetFocus); + +begin + inherited; + if (FSelectionCount > 0) or not (toGhostedIfUnfocused in FOptions.PaintOptions) then + Invalidate; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMSize(var Message: TWMSize); + +begin + inherited; + + // Need to update scroll bars here. This will cause a recursion because of the change of the client area + // when changing a scrollbar. Usually this is no problem since with the second level recursion no change of the + // window size happens (the same values for the scrollbars are set, which shouldn't cause a window size change). + // Appearently, this applies not to all systems, however. + if HandleAllocated and ([tsSizing, tsWindowCreating] * FStates = []) and (ClientHeight > 0) then + try + DoStateChange([tsSizing]); + // This call will invalidate the entire non-client area which needs recalculation on resize. + TVTHeaderCracker(FHeader).RescaleHeader; + TVTHeaderCracker(FHeader).UpdateSpringColumns; + UpdateScrollBars(True); + + if (tsEditing in FStates) and not FHeader.UseColumns then + UpdateEditBounds; + finally + DoStateChange([], [tsSizing]); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMThemeChanged(var Message: TMessage); + +begin + inherited; + + if StyleServices.Enabled and (toThemeAware in TreeOptions.PaintOptions) then + DoStateChange([tsUseThemes]) + else + DoStateChange([], [tsUseThemes]); + + // Updating the visuals here will not work correctly. Therefore we postpone + // the update by using a timer. + if not FChangingTheme then + SetTimer(Handle, ThemeChangedTimer, ThemeChangedTimerDelay, nil); + FChangingTheme := False; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMTimer(var Message: TWMTimer); + +// centralized timer handling happens here + +begin + with Message do + begin + case TimerID of + ExpandTimer: + DoDragExpand; + EditTimer: + DoEdit; + ScrollTimer: + begin + if tsScrollPending in FStates then + begin + Application.CancelHint; + // Scroll delay has elapsed, set to normal scroll interval now. + SetTimer(Handle, ScrollTimer, FAutoScrollInterval, nil); + DoStateChange([tsScrolling], [tsScrollPending]); + end; + DoTimerScroll; + end; + ChangeTimer: + if tsChangePending in FStates then // see issue #602 + DoChange(FLastChangedNode); + StructureChangeTimer: + DoStructureChange(FLastStructureChangeNode, FLastStructureChangeReason); + SearchTimer: + begin + // When this event triggers then the user did not pressed any key for the specified timeout period. + // Hence incremental searching is stopped. + DoStateChange([], [tsIncrementalSearching]); + StopTimer(SearchTimer); + FSearchBuffer := ''; + FLastSearchNode := nil; + end; + ThemeChangedTimer: + begin + StopTimer(ThemeChangedTimer); + RecreateWnd; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WMVScroll(var Message: TWMVScroll); + + //--------------- local functions ------------------------------------------- + + function GetRealScrollPosition: TDimension; + + var + SI: TScrollInfo; + Bar: Integer; + + begin + SI.cbSize := SizeOf(TScrollInfo); + SI.fMask := SIF_TRACKPOS; + Bar := SB_VERT; + GetScrollInfo(Bar, SI); + Result := SI.nTrackPos; + end; + + //--------------- end local functions --------------------------------------- + +begin + case Message.ScrollCode of + SB_BOTTOM: + SetOffsetY(-FRoot.TotalHeight); + SB_ENDSCROLL: + begin + DoStateChange([], [tsThumbTracking]); + // Avoiding to adjust the horizontal scroll position while tracking makes scrolling much smoother + // but we need to adjust the final position here then. + UpdateScrollBars(True); + // Really weird invalidation needed here (and I do it only because it happens so rarely), because + // when showing the horizontal scrollbar while scrolling down using the down arrow button, + // the button will be repainted on mouse up (at the wrong place in the far right lower corner)... + RedrawWindow(nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN); + end; + SB_LINEUP: + SetOffsetY(FOffsetY + FScrollBarOptions.VerticalIncrement); + SB_LINEDOWN: + SetOffsetY(FOffsetY - FScrollBarOptions.VerticalIncrement); + SB_PAGEUP: + SetOffsetY(FOffsetY + ClientHeight); + SB_PAGEDOWN: + SetOffsetY(FOffsetY - ClientHeight); + + SB_THUMBPOSITION, + SB_THUMBTRACK: + begin + DoStateChange([tsThumbTracking]); + SetOffsetY(-GetRealScrollPosition); + end; + SB_TOP: + SetOffsetY(0); + end; + Message.Result := 0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.AddToSelection(Node: PVirtualNode; NotifySynced: Boolean); + +var + Changed: Boolean; + RemoveSyncAfterChange: Boolean; +begin + if not FSelectionLocked then + begin + Assert(Assigned(Node), 'Node must not be nil!'); + Changed := InternalAddToSelection(Node, False); + if Changed then + begin + UpdateNextNodeToSelect(Node); + if (SelectedCount = 1) then + FocusedNode := Node; // if only one node is selected, make sure the focused node changes with the selected node + InvalidateNode(Node); + RemoveSyncAfterChange := NotifySynced and not (tsSynchMode in fStates); + if RemoveSyncAfterChange then + Include(FStates, tsSynchMode); + try + Change(Node); + finally + if RemoveSyncAfterChange then + Exclude(FStates, tsSynchMode); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.AddToSelection(const NewItems: TNodeArray; NewLength: Integer; ForceInsert: Boolean = False); + +// Adds the given items all at once into the current selection array. NewLength is the amount of +// nodes to add (necessary to allow NewItems to be larger than the actual used entries). +// ForceInsert is True if nodes must be inserted without consideration of level select constraint or +// already set selected flags (e.g. when loading from stream). +// Note: In the case ForceInsert is True the caller is responsible for making sure the new nodes aren't already in the +// selection array! + +var + Changed: Boolean; + +begin + Changed := InternalAddToSelection(NewItems, NewLength, ForceInsert); + if Changed then + begin + if NewLength = 1 then + begin + InvalidateNode(NewItems[0]); + Change(NewItems[0]); + end + else + begin + Invalidate; + Change(nil); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; var NextNonEmpty: TColumnIndex); + +// Used in descendants to modify the paint rectangle of the current column while painting a certain node. + +begin + // Since cells are always drawn from left to right the next column index is independent of the + // bidi mode, but not the column borders, which might change depending on the cell's content. + NextNonEmpty := FHeader.Columns.GetNextVisibleColumn(PaintInfo.Column); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.AdjustPanningCursor(X, Y: TDimension); + +// Triggered by a mouse move when wheel panning/scrolling is active. +// Loads the proper cursor which indicates into which direction scrolling is done. + +var + NewCursor: TPanningCursor; + NewCursorHandle: HCURSOR; + ScrollHorizontal, + ScrollVertical: Boolean; + +begin + ScrollHorizontal := FRangeX > ClientWidth; + ScrollVertical := FRangeY > ClientHeight; + + if (Abs(X - FLastClickPos.X) < 8) and (Abs(Y - FLastClickPos.Y) < 8) then + begin + // Mouse is in the neutral zone. + if ScrollHorizontal then + begin + if ScrollVertical then + NewCursor := TPanningCursor.MOVEALL + else + NewCursor := TPanningCursor.MOVEEW; + end + else + NewCursor := TPanningCursor.MOVENS; + end + else + begin + // One of 8 directions applies: north, north-east, east, south-east, south, south-west, west and north-west. + // Check also if scrolling in the particular direction is possible. + if ScrollVertical and ScrollHorizontal then + begin + // All directions allowed. + if X - FLastClickPos.X < -8 then + begin + // Left hand side. + if Y - FLastClickPos.Y < -8 then + NewCursor := TPanningCursor.MOVENW + else + if Y - FLastClickPos.Y > 8 then + NewCursor := TPanningCursor.MOVESW + else + NewCursor := TPanningCursor.MOVEW; + end + else + if X - FLastClickPos.X > 8 then + begin + // Right hand side. + if Y - FLastClickPos.Y < -8 then + NewCursor := TPanningCursor.MOVENE + + else + if Y - FLastClickPos.Y > 8 then + NewCursor := TPanningCursor.MOVESE + else + NewCursor := TPanningCursor.MOVEE; + end + else + begin + // Up or down. + if Y < FLastClickPos.Y then + NewCursor := TPanningCursor.MOVEN + else + NewCursor := TPanningCursor.MOVES; + end; + end + else + if ScrollHorizontal then + begin + // Only horizontal movement allowed. + if X < FLastClickPos.X then + NewCursor := TPanningCursor.MOVEW + else + NewCursor := TPanningCursor.MOVEE; + end + else + begin + // Only vertical movement allowed. + if Y < FLastClickPos.Y then + NewCursor := TPanningCursor.MOVEN + else + NewCursor := TPanningCursor.MOVES; + end; + end; + + // Now load the cursor and apply it. + NewCursorHandle := LoadCursor(0, MAKEINTRESOURCE(NewCursor)); + if FPanningCursor <> NewCursorHandle then + begin + DeleteObject(FPanningCursor); + FPanningCursor := NewCursorHandle; + Winapi.Windows.SetCursor(FPanningCursor); + end + else + DeleteObject(NewCursorHandle); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.AdviseChangeEvent(StructureChange: Boolean; Node: PVirtualNode; Reason: TChangeReason); + +// Used to register a delayed change event. If StructureChange is False then we have a selection change event (without +// a specific reason) otherwise it is a structure change. + +begin + if StructureChange then + begin + if tsStructureChangePending in FStates then + StopTimer(StructureChangeTimer) + else + DoStateChange([tsStructureChangePending]); + + FLastStructureChangeNode := Node; + if FLastStructureChangeReason = crIgnore then + FLastStructureChangeReason := Reason + else + if Reason <> crIgnore then + FLastStructureChangeReason := crAccumulated; + end + else + begin + if tsChangePending in FStates then + StopTimer(ChangeTimer) + else + DoStateChange([tsChangePending]); + + FLastChangedNode := Node; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.AllocateInternalDataArea(Size: Cardinal): Cardinal; + +// Simple registration method to be called by each descendant to claim their internal data area. +// Result is the offset from the begin of the node to the internal data area of the calling tree class. + +begin + Assert((FRoot = nil) or (FRoot.ChildCount = 0), 'Internal data allocation must be done before any node is created.'); + Result := TreeNodeSize + FTotalInternalDataSize; + System.Inc(FTotalInternalDataSize, (Size + (SizeOf(Pointer) - 1)) and not (SizeOf(Pointer) - 1)); + InitRootNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.Animate(Steps, Duration: Cardinal; Callback: TVTAnimationCallback; Data: Pointer); + +// This method does the calculation part of an animation as used for node toggling and hint animations. +// Steps is the maximum amount of animation steps to do and Duration determines the milliseconds the animation +// has to run. Callback is a task specific method which is called in the loop for every step and Data is simply +// something to pass on to the callback. +// The callback is called with the current step, the current step size and the Data parameter. Since the step amount +// as well as the step size are possibly adjusted during the animation, it is impossible to determine if the current +// step is the last step, even if the original step amount is known. To solve this problem the callback will be +// called after the loop has finished with a step size of 0 indicating so to execute any post processing. + +var + StepSize, + RemainingTime, + RemainingSteps, + NextTimeStep, + CurrentStep, + StartTime: Cardinal; + CurrentTime: Int64; + +begin + if not (tsInAnimation in FStates) and (Duration > 0) then + begin + DoStateChange([tsInAnimation]); + try + RemainingTime := Duration; + RemainingSteps := Steps; + + // Determine the initial step size which is either 1 if the needed steps are less than the number of + // steps possible given by the duration or > 1 otherwise. + StepSize := Round(Max(1, RemainingSteps / Duration)); + RemainingSteps := RemainingSteps div StepSize; + CurrentStep := 0; + + while (RemainingSteps > 0) and (RemainingTime > 0) and not Application.Terminated do + begin + StartTime := timeGetTime; + NextTimeStep := StartTime + RemainingTime div RemainingSteps; + if not Callback(CurrentStep, StepSize, Data) then + Break; + + // Keep duration for this step for rest calculation. + CurrentTime := timeGetTime; + // Wait until the calculated time has been reached. + while CurrentTime < NextTimeStep do + CurrentTime := timeGetTime; + + // Subtract the time this step really needed. + if RemainingTime >= CurrentTime - StartTime then + begin + System.Dec(RemainingTime, CurrentTime - StartTime); + System.Dec(RemainingSteps); + end + else + begin + RemainingTime := 0; + RemainingSteps := 0; + end; + // If the remaining time per step is less than one time step then we have to decrease the + // step count and increase the step size. + if (RemainingSteps > 0) and ((RemainingTime div RemainingSteps) < 1) then + begin + repeat + System.Inc(StepSize); + RemainingSteps := RemainingTime div StepSize; + until (RemainingSteps <= 0) or ((RemainingTime div RemainingSteps) >= 1); + end; + CurrentStep := Steps - RemainingSteps; + end; + + if not Application.Terminated then + Callback(0, 0, Data); + finally + DoStateChange([], [tsInAnimation]); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.StartOperation(OperationKind: TVTOperationKind); + +// Called to indicate that a long-running operation has been started. + +begin + System.Inc(FOperationCount); + if FOperationCount = 1 then + FOperationCanceled := False; + DoStartOperation(OperationKind); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CalculateSelectionRect(X, Y: TDimension): Boolean; + +// Recalculates old and new selection rectangle given that X, Y are new mouse coordinates. +// Returns True if there was a change since the last call. + +var + MaxValue: TDimension; + +begin + if tsDrawSelecting in FStates then + FLastSelRect := FNewSelRect; + FNewSelRect.BottomRight := Point(X + FEffectiveOffsetX, Y - FOffsetY); + if FNewSelRect.Right < 0 then + FNewSelRect.Right := 0; + if FNewSelRect.Bottom < 0 then + FNewSelRect.Bottom := 0; + MaxValue := ClientWidth; + if FRangeX > MaxValue then + MaxValue := FRangeX; + if FNewSelRect.Right > MaxValue then + FNewSelRect.Right := MaxValue; + MaxValue := ClientHeight; + if FRangeY > MaxValue then + MaxValue := FRangeY; + if FNewSelRect.Bottom > MaxValue then + FNewSelRect.Bottom := MaxValue; + + Result := not CompareMem(@FLastSelRect, @FNewSelRect, SizeOf(FNewSelRect)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CanAutoScroll: Boolean; + +// Determines if auto scrolling is currently allowed. + +var + IsDropTarget: Boolean; + IsDrawSelecting: Boolean; + IsWheelPanning: Boolean; + +begin + // Don't scroll the client area if the header is currently doing tracking or dragging. + // Do auto scroll only if there is a draw selection in progress or the tree is the current drop target or + // wheel panning/scrolling is active. + IsDropTarget := Assigned(FDragManager) and DragManager.IsDropTarget; + IsDrawSelecting := [tsDrawSelPending, tsDrawSelecting] * FStates <> []; + IsWheelPanning := tsPanning in FStates; + Result := ((toAutoScroll in FOptions.AutoOptions) or IsWheelPanning) and + (FHeader.States = []) and (IsDrawSelecting or IsDropTarget or (tsVCLDragging in FStates) or IsWheelPanning); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CanShowDragImage: Boolean; + +// Determines whether a drag image should be shown. + +begin + Result := FDragImageKind <> diNoImage; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex): Boolean; + +begin + Result := (toNodeHeightResize in FOptions.MiscOptions) and Assigned(Node) and (Node <> FRoot) and + (Column > NoColumn) and (coFixed in FHeader.Columns[Column].Options); + DoCanSplitterResizeNode(P, Node, Column, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.Change(Node: PVirtualNode); + +begin + AdviseChangeEvent(False, Node, crIgnore); + + if FUpdateCount = 0 then + begin + if (FChangeDelay > 0) and HandleAllocated and not (tsSynchMode in FStates) then + SetTimer(Handle, ChangeTimer, FChangeDelay, nil) + else + DoChange(Node); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ChangeScale(M, D: Integer{$if CompilerVersion >= 31}; isDpiChange: Boolean{$ifend}); +begin + if (M <> D) then + begin + BeginUpdate(); + try + ScaleNodeHeights(M, D); + SetDefaultNodeHeight(MulDiv(FDefaultNodeHeight, M, D)); + Indent := MulDiv(Indent, M, D); + FTextMargin := MulDiv(FTextMargin, M, D); + FMargin := MulDiv(FMargin, M, D); + FImagesMargin := MulDiv(FImagesMargin, M, D); + finally + EndUpdate(); + end;//try..finally + end;// if M<>D + inherited ChangeScale(M, D{$if CompilerVersion >= 31}, isDpiChange{$ifend}); + if (M <> D) then + begin + // Scale header + TVTHeaderCracker(FHeader).ChangeScale(M, D); + // Scale utility images, #796 + if FCheckImageKind = ckSystemDefault then begin + FreeAndNil(FCheckImages); + if HandleAllocated then + FCheckImages := CreateSystemImageSet(); + end; + UpdateHeaderRect(); + PrepareBitmaps(True, False); // See issue #991 + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ScaleNodeHeights(M, D: TDimension); +var + Run: PVirtualNode; + lNewNodeTotalHeight: Cardinal; +begin + // Scale also node heights + BeginUpdate(); + try + Run := GetFirstNoInit(); + while Assigned(Run) do + begin + if vsInitialized in Run.States then + SetNodeHeight(Run, MulDiv(Run.NodeHeight, M, D)) + else // prevent initialization of non-initialzed nodes + begin + Run.SetNodeHeight(MulDiv(Run.NodeHeight, M, D)); + // The next three lines fix issue #1000 + lNewNodeTotalHeight := MulDiv(Run.TotalHeight, M, D); + FRoot.TotalHeight := Cardinal(Int64(FRoot.TotalHeight) + Int64(lNewNodeTotalHeight) - Int64(Run.TotalHeight)); // Avoiding EIntOverflow exception. + Run.TotalHeight := lNewNodeTotalHeight; + end; + Run := GetNextNoInit(Run); + end; // while + finally + EndUpdate(); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ChangeTreeStatesAsync(EnterStates, LeaveStates: TVirtualTreeStates); +begin + //TODO: If this works reliable, move to TWorkerThread + if not (csDestroying in ComponentState) then + begin + AtomicIncrement(FPendingSyncProcs); + TThread.Synchronize(nil, procedure + begin + //Decrement invoke refs + AtomicDecrement(FPendingSyncProcs); + // Prevent invalid combination tsUseCache + tsValidationNeeded (#915) + if not ((tsUseCache in EnterStates) and (tsValidationNeeded in FStates + LeaveStates)) then + DoStateChange(EnterStates, LeaveStates); + if (tsValidating in FStates) and (tsValidating in LeaveStates) then + UpdateEditBounds(); + end); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CheckParentCheckState(Node: PVirtualNode; NewCheckState: TCheckState): Boolean; + +// Checks all siblings of node to determine which check state Node's parent must get. + +var + CheckCount, + BoxCount: Cardinal; + PartialCheck: Boolean; + Run: PVirtualNode; + +begin + CheckCount := 0; + BoxCount := 0; + PartialCheck := False; + Run := Node.Parent.FirstChild; + while Assigned(Run) do + begin + if Run = Node then + begin + // The given node cannot be checked because it does not yet have its new check state (as this depends + // on the outcome of this method). Instead NewCheckState is used as this contains the new state the node + // will get if this method returns True. + if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then + begin + System.Inc(BoxCount); + if NewCheckState.IsChecked then + System.Inc(CheckCount); + PartialCheck := PartialCheck or (NewCheckState = csMixedNormal); + end; + end + else + if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then + begin + System.Inc(BoxCount); + if GetCheckState(Run).IsChecked then + System.Inc(CheckCount); + PartialCheck := PartialCheck or (GetCheckState(Run) = csMixedNormal); + end; + Run := Run.NextSibling; + end; + + if (CheckCount = 0) and not PartialCheck then + NewCheckState := csUncheckedNormal + else + if CheckCount < BoxCount then + NewCheckState := csMixedNormal + else + NewCheckState := csCheckedNormal; + + Node := Node.Parent; + Result := DoChecking(Node, NewCheckState); + if Result then + begin + DoCheckClick(Node, NewCheckState); + // Recursively adjust parent of parent. + // This is already done in the function DoCheckClick() called in the above line + // We revent unnecessary upward recursion by commenting this code. + // with Node^ do + // begin + // if not (vsInitialized in Parent.States) then + // InitNode(Parent); + // if ([vsChecking, vsDisabled] * Parent.States = []) and (Parent <> FRoot) and + // (Parent.CheckType = ctTriStateCheckBox) then + // Result := CheckParentCheckState(Node, NewCheckState); + // end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ClearTempCache; + +// make sure the temporary node cache is in a reliable state + +begin + FTempNodeCache := nil; + FTempNodeCount := 0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean; + +// Returns True if the given column is to be considered as being empty. This will usually be determined by +// descendants as the base tree implementation has not enough information to decide. + +begin + Result := True; + if Assigned(FOnGetCellIsEmpty) then + FOnGetCellIsEmpty(Self, Node, Column, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ComputeRTLOffset(ExcludeScrollBar: Boolean): TDimension; + +// Computes the horizontal offset needed when all columns are automatically right aligned (in RTL bidi mode). +// ExcludeScrollBar determines if the left-hand vertical scrollbar is to be included (if visible) or not. + +var + HeaderWidth: TDimension; + ScrollBarVisible: Boolean; +begin + ScrollBarVisible := (FRangeY > ClientHeight) and (ScrollBarOptions.ScrollBars in [TScrollStyle.ssVertical, TScrollStyle.ssBoth]); + if ScrollBarVisible then + Result := GetSystemMetrics(SM_CXVSCROLL) + else + Result := 0; + + // Make everything right aligned. + HeaderWidth := FHeaderRect.Right - FHeaderRect.Left; + if FRangeX + Result <= HeaderWidth then + Result := HeaderWidth - FRangeX; + // Otherwise take only left-hand vertical scrollbar into account. + + if ScrollBarVisible and ExcludeScrollBar then + Dec(Result, GetSystemMetrics(SM_CXVSCROLL)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CountLevelDifference(Node1, Node2: PVirtualNode): Integer; + +// This method counts how many indentation levels the given nodes are apart. If both nodes have the same parent then the +// difference is 0 otherwise the result is basically GetNodeLevel(Node2) - GetNodeLevel(Node1), but with sign. +// If the result is negative then Node2 is less intended than Node1. + +var + Level1, Level2: Integer; + +begin + Assert(Assigned(Node1) and Assigned(Node2), 'Both nodes must be Assigned.'); + + Level1 := 0; + while Node1.Parent <> FRoot do + begin + System.Inc(Level1); + Node1 := Node1.Parent; + end; + + Level2 := 0; + while Node2.Parent <> FRoot do + begin + System.Inc(Level2); + Node2 := Node2.Parent; + end; + + Result := Level2 - Level1; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CountVisibleChildren(Node: PVirtualNode): Cardinal; + +// Returns the number of visible child nodes of the given node. + +begin + Result := 0; + + // The node's direct children... + if vsExpanded in Node.States then + begin + // ...and their children. + Node := Node.FirstChild; + while Assigned(Node) do + begin + if vsVisible in Node.States then + System.Inc(Result, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1))); + Node := Node.NextSibling; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CreateParams(var Params: TCreateParams); + +const + ScrollBar: array[TScrollStyle] of Cardinal = (0, WS_HSCROLL, WS_VSCROLL, WS_HSCROLL or WS_VSCROLL); + +begin + inherited CreateParams(Params); + + with Params do + begin + Style := Style or WS_CLIPCHILDREN or WS_CLIPSIBLINGS or ScrollBar[ScrollBarOptions.ScrollBars]; + if toFullRepaintOnResize in FOptions.MiscOptions then + WindowClass.style := WindowClass.style or CS_HREDRAW or CS_VREDRAW + else + WindowClass.style := WindowClass.style and not (CS_HREDRAW or CS_VREDRAW); + if FBorderStyle = bsSingle then + begin + if Ctl3D then + begin + ExStyle := ExStyle or WS_EX_CLIENTEDGE; + Style := Style and not WS_BORDER; + end + else + Style := Style or WS_BORDER; + end + else + Style := Style and not WS_BORDER; + + AddBiDiModeExStyle(ExStyle); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CreateWnd; + +// Initializes data which depends on a valid window handle. + +begin + VclStyleChanged(); // Moved here due to issue #986 + DoStateChange([tsWindowCreating]); + inherited; + DoStateChange([], [tsWindowCreating]); + + if not Assigned(FCheckImages) then + FCheckImages := CreateSystemImageSet(); + + if ((StyleServices.Enabled ) and (toThemeAware in TreeOptions.PaintOptions) ) then + begin + DoStateChange([tsUseThemes]); + if (toUseExplorerTheme in FOptions.PaintOptions) then + begin + DoStateChange([tsUseExplorerTheme]); + SetWindowTheme('explorer'); + end + else + DoStateChange([], [tsUseExplorerTheme]); + end + else + DoStateChange([], [tsUseThemes, tsUseExplorerTheme]); + + AutoScale(); + // Because of the special recursion and update stopper when creating the window (or resizing it) + // we have to manually trigger the auto size calculation here. + if hsNeedScaling in FHeader.States then + TVTHeaderCracker(FHeader).RescaleHeader; + if hoAutoResize in FHeader.Options then + TVirtualTreeColumnsCracker(FHeader.Columns).AdjustAutoSize(InvalidColumn); + + PrepareBitmaps(True, True); + + // Register tree as OLE drop target. + if not (csDesigning in ComponentState) and not (csLoading in ComponentState) and ((hoDrag in Header.Options) or (toAcceptOLEDrop in TreeOptions.MiscOptions)) then // will be done in Loaded after all inherited settings are loaded from the DFMs + RegisterDragDrop(Handle, DragManager as IDropTarget); + + UpdateScrollBars(True); + UpdateHeaderRect; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.FakeReadIdent(Reader: TReader); +begin + Assert(Reader.NextValue = vaIdent); + Reader.ReadIdent; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DecVisibleCount; +begin + System.Dec(FVisibleCount); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DefineProperties(Filer: TFiler); + +// There were heavy changes in some properties during development of VT. This method helps to make migration easier +// by reading old properties manually and put them into the new properties as appropriate. +// Note: these old properties are never written again and silently disappear. +// June 2002: Meanwhile another task is done here too: working around the problem that TCollection is not streamed +// correctly when using Visual Form Inheritance (VFI). + +var + StoreIt: Boolean; + +begin + inherited; + + // The header can prevent writing columns altogether. + if TVTHeaderCracker(FHeader).CanWriteColumns then + begin + // Check if we inherit from an ancestor form (Visual Form Inheritance). + StoreIt := Filer.Ancestor = nil; + // If there is an ancestor then save columns only if they are different to the base set. + if not StoreIt then + StoreIt := not FHeader.Columns.Equals(TBaseVirtualTree(Filer.Ancestor).FHeader.Columns); + end + else + StoreIt := False; + + Filer.DefineProperty('Columns', TVTHeaderCracker(FHeader).ReadColumns, TVTHeaderCracker(FHeader).WriteColumns, StoreIt); + + // #622 made old DFMs incompatible with new VTW - so the program is compiled successfully + // and then suddenly crashes at user site in runtime. + Filer.DefineProperty('CheckImageKind', FakeReadIdent, nil, false); + /// #730 removed property HintAnimation + Filer.DefineProperty('HintAnimation', FakeReadIdent, nil, false); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DetermineDropMode(const P: TPoint; var HitInfo: THitInfo; var NodeRect: TRect): TDropMode; + +// Determine the DropMode. + +var + ImageHit: Boolean; + LabelHit: Boolean; + ItemHit: Boolean; + +begin + ImageHit := HitInfo.HitPositions * [hiOnNormalIcon, hiOnStateIcon] <> []; + LabelHit := hiOnItemLabel in HitInfo.HitPositions; + ItemHit := (hiOnItem in HitInfo.HitPositions); + + // In report mode only direct hits of the node captions/images in the main column are accepted as hits. + if (toReportMode in FOptions.MiscOptions) and not (ItemHit or ((LabelHit or ImageHit) and + (HitInfo.HitColumn = FHeader.MainColumn))) then + HitInfo.HitNode := nil; + + if Assigned(HitInfo.HitNode) then + begin + if LabelHit or ImageHit or not (toShowDropmark in FOptions.PaintOptions) then + Result := dmOnNode + else + if Divide(NodeRect.Top + NodeRect.Bottom, 2) > P.Y then + Result := dmAbove + else + Result := dmBelow; + end + else + Result := dmNowhere; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DetermineHiddenChildrenFlag(Node: PVirtualNode); + +// Update the hidden children flag of the given node. + +var + Run: PVirtualNode; + +begin + if Node.ChildCount = 0 then + begin + if vsHasChildren in Node.States then + Exclude(Node.States, vsAllChildrenHidden) + else + Include(Node.States, vsAllChildrenHidden); + end + else + begin + // Iterate through all siblings and stop when one visible is found. + Run := Node.FirstChild; + while Assigned(Run) and not IsEffectivelyVisible[Run] do + Run := Run.NextSibling; + if Assigned(Run) then + Exclude(Node.States, vsAllChildrenHidden) + else + Include(Node.States, vsAllChildrenHidden); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DetermineHiddenChildrenFlagAllNodes; + +var + Run: PVirtualNode; + +begin + Run := GetFirstNoInit(False); + while Assigned(Run) do + begin + DetermineHiddenChildrenFlag(Run); + Run := GetNextNoInit(Run); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DetermineHitPositionLTR(var HitInfo: THitInfo; Offset, Right: TDimension; + Alignment: TAlignment); + +// This method determines the hit position within a node with left-to-right orientation. + +var + MainColumnHit: Boolean; + lIndent, + TextWidth, + ImageOffset: TDimension; + lOffsets: TVTOffsets; +begin + MainColumnHit := HitInfo.HitColumn = FHeader.MainColumn; + GetOffsets(HitInfo.HitNode, lOffsets, ofsRightOfText, HitInfo.HitColumn); + + if (MainColumnHit and (Offset < lOffsets[ofsCheckbox])) then + begin + // Position is to the left of calculated indentation which can only happen for the main column. + // Check whether it corresponds to a button/checkbox. + if (toShowButtons in FOptions.PaintOptions) and (vsHasChildren in HitInfo.HitNode.States) then + begin + // Position of button is interpreted very generously to avoid forcing the user + // to click exactly into the 9x9 pixels area. The entire node height and one full + // indentation level is accepted as button hit. + if Offset >= lOffsets[ofsCheckbox] - FIndent then + Include(HitInfo.HitPositions, hiOnItemButton); + if Offset > lOffsets[ofsToggleButton] then + Include(HitInfo.HitPositions, hiOnItemButtonExact); + end; + // no button hit so position is on indent + if HitInfo.HitPositions = [] then + Include(HitInfo.HitPositions, hiOnItemIndent); + end + else + begin + // The next hit positions can be: + // - on the check box + // - on the state image + // - on the normal image + // - to the left of the text area + // - on the label or + // - to the right of the text area + // (in this order). + + // In report mode no hit other than in the main column is possible. + if MainColumnHit or not (toReportMode in FOptions.MiscOptions) then + begin + if MainColumnHit and (Offset < lOffsets[ofsStateImage]) then + begin + HitInfo.HitPositions := [hiOnItem]; + if (HitInfo.HitNode.CheckType <> ctNone) then + Include(HitInfo.HitPositions, hiOnItemCheckBox); + end + else + begin + ImageOffset := lOffsets[ofsImage]; + if Offset < ImageOffset then + Include(HitInfo.HitPositions, hiOnStateIcon) + else + begin + ImageOffset := lOffsets[ofsLabel]; + if Offset < ImageOffset then + Include(HitInfo.HitPositions, hiOnNormalIcon) + else + begin + TextWidth := lOffsets[ofsRightOfText] - lOffsets[ofsText]; + // ImageOffset contains now the left border of the node label area. This is used to calculate the + // correct alignment in the column. + + // Check if the text can be aligned at all. This is only possible if there is enough room + // in the remaining text rectangle. + if TextWidth > Right - ImageOffset then + Include(HitInfo.HitPositions, hiOnItemLabel) + else + begin + case Alignment of + taCenter: + begin + lIndent := Divide(ImageOffset + Right - TextWidth, 2); + if Offset < lIndent then + Include(HitInfo.HitPositions, hiOnItemLeft) + else + if Offset < lIndent + TextWidth then + Include(HitInfo.HitPositions, hiOnItemLabel) + else + Include(HitInfo.HitPositions, hiOnItemRight); + end; + taRightJustify: + begin + lIndent := Right - TextWidth; + if Offset < lIndent then + Include(HitInfo.HitPositions, hiOnItemLeft) + else + Include(HitInfo.HitPositions, hiOnItemLabel); + end; + else // taLeftJustify + if Offset < ImageOffset + TextWidth then + Include(HitInfo.HitPositions, hiOnItemLabel) + else + Include(HitInfo.HitPositions, hiOnItemRight); + end; + end; + end; + end; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DetermineHitPositionRTL(var HitInfo: THitInfo; Offset, Right: TDimension; Alignment: TAlignment); + +// This method determines the hit position within a node with right-to-left orientation. + +var + MainColumnHit: Boolean; + Run: PVirtualNode; + Indent, + TextWidth, + ImageOffset: TDimension; + +begin + MainColumnHit := HitInfo.HitColumn = FHeader.MainColumn; + + // If columns are not used or the main column is hit then the tree indentation must be considered too. + if MainColumnHit then + begin + if toFixedIndent in FOptions.PaintOptions then + Dec(Right, FIndent) + else + begin + Run := HitInfo.HitNode; + while (Run.Parent <> FRoot) do + begin + Dec(Right, FIndent); + Run := Run.Parent; + end; + if toShowRoot in FOptions.PaintOptions then + Dec(Right, FIndent); + end; + end; + + if Offset >= Right then + begin + // Position is to the right of calculated indentation which can only happen for the main column. + // Check whether it corresponds to a button/checkbox. + if (toShowButtons in FOptions.PaintOptions) and (vsHasChildren in HitInfo.HitNode.States) then + begin + // Position of button is interpreted very generously to avoid forcing the user + // to click exactly into the 9x9 pixels area. The entire node height and one full + // indentation level is accepted as button hit. + if Offset <= Right + FIndent then + Include(HitInfo.HitPositions, hiOnItemButton); + if Offset <= Right + FPlusBM.Width then + Include(HitInfo.HitPositions, hiOnItemButtonExact); + end; + // no button hit so position is on indent + if HitInfo.HitPositions = [] then + Include(HitInfo.HitPositions, hiOnItemIndent); + end + else + begin + // The next hit positions can be: + // - on the check box + // - on the state image + // - on the normal image + // - to the left of the text area + // - on the label or + // - to the right of the text area + // (in this order). + + // In report mode no hit other than in the main column is possible. + if MainColumnHit or not (toReportMode in FOptions.MiscOptions) then + begin + ImageOffset := Right - FMargin; + + // Check support is only available for the main column. + if MainColumnHit and (toCheckSupport in FOptions.MiscOptions) and Assigned(FCheckImages) and + (HitInfo.HitNode.CheckType <> ctNone) then + Dec(ImageOffset, FCheckImages.Width + FImagesMargin); + + if MainColumnHit and (Offset > ImageOffset) then + begin + HitInfo.HitPositions := [hiOnItem]; + if (HitInfo.HitNode.CheckType <> ctNone) then + Include(HitInfo.HitPositions, hiOnItemCheckBox); + end + else + begin + Dec(ImageOffset, GetImageSize(HitInfo.HitNode, ikState, HitInfo.HitColumn).cx); + if Offset > ImageOffset then + Include(HitInfo.HitPositions, hiOnStateIcon) + else + begin + Dec(ImageOffset, GetImageSize(HitInfo.HitNode, ikNormal, HitInfo.HitColumn).cx); + if Offset > ImageOffset then + Include(HitInfo.HitPositions, hiOnNormalIcon) + else + begin + // ImageOffset contains now the right border of the node label area. This is used to calculate the + // correct alignment in the column. + TextWidth := DoGetNodeWidth(HitInfo.HitNode, HitInfo.HitColumn); + + // Check if the text can be aligned at all. This is only possible if there is enough room + // in the remaining text rectangle. + if TextWidth > ImageOffset then + Include(HitInfo.HitPositions, hiOnItemLabel) + else + begin + // Consider bidi mode here. In RTL context does left alignment actually mean right alignment + // and vice versa. + ChangeBiDiModeAlignment(Alignment); + + case Alignment of + taCenter: + begin + Indent := Divide(ImageOffset - TextWidth, 2); + if Offset < Indent then + Include(HitInfo.HitPositions, hiOnItemLeft) + else + if Offset < Indent + TextWidth then + Include(HitInfo.HitPositions, hiOnItemLabel) + else + Include(HitInfo.HitPositions, hiOnItemRight); + end; + taRightJustify: + begin + Indent := ImageOffset - TextWidth; + if Offset < Indent then + Include(HitInfo.HitPositions, hiOnItemLeft) + else + Include(HitInfo.HitPositions, hiOnItemLabel); + end; + else // taLeftJustify + if Offset > TextWidth then + Include(HitInfo.HitPositions, hiOnItemRight) + else + Include(HitInfo.HitPositions, hiOnItemLabel); + end; + end; + end; + end; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DetermineLineImageAndSelectLevel(Node: PVirtualNode; var LineImage: TLineImage): Integer; + +// This method is used during paint cycles and initializes an array of line type IDs. These IDs are used to paint +// the tree lines in front of the given node. +// Additionally an initial count of selected parents is determined and returned which is used for specific painting. + +var + X: Integer; + Indent: Integer; + Run: PVirtualNode; + +begin + Result := 0; + if toShowRoot in FOptions.PaintOptions then + X := 1 + else + X := 0; + Run := Node; + // Determine indentation level of top node. + while Run.Parent <> FRoot do + begin + System.Inc(X); + Run := Run.Parent; + // Count selected nodes (FRoot is never selected). + if vsSelected in Run.States then + System.Inc(Result); + end; + + // Set initial size of line index array, this will automatically initialized all entries to ltNone. + SetLength(LineImage, X); + Indent := X - 1; + + // Only use lines if requested. + if (toShowTreeLines in FOptions.PaintOptions) and + (not (toHideTreeLinesIfThemed in FOptions.PaintOptions) or not (tsUseThemes in FStates)) then + begin + if toChildrenAbove in FOptions.PaintOptions then + begin + System.Dec(X); + if not HasVisiblePreviousSibling(Node) then + begin + if (Node.Parent <> FRoot) or HasVisibleNextSibling(Node) then + LineImage[X] := ltBottomRight + else + LineImage[X] := ltRight; + end + else + if (Node.Parent = FRoot) and (not HasVisibleNextSibling(Node)) then + LineImage[X] := ltTopRight + else + LineImage[X] := ltTopDownRight; + + // Now go up to the root to determine the rest. + Run := Node.Parent; + while Run <> FRoot do + begin + System.Dec(X); + if HasVisiblePreviousSibling(Run) then + LineImage[X] := ltTopDown + else + LineImage[X] := ltNone; + + Run := Run.Parent; + end; + end + else + begin + // Start over parent traversal if necessary. + Run := Node; + + if Run.Parent <> FRoot then + begin + // The very last image (the one immediately before the item label) is different. + if HasVisibleNextSibling(Run) then + LineImage[X - 1] := ltTopDownRight + else + LineImage[X - 1] := ltTopRight; + Run := Run.Parent; + + // Now go up all parents. + repeat + if Run.Parent = FRoot then + Break; + System.Dec(X); + if HasVisibleNextSibling(Run) then + LineImage[X - 1] := ltTopDown + else + LineImage[X - 1] := ltNone; + Run := Run.Parent; + until False; + end; + + // Prepare root level. Run points at this stage to a top level node. + if (toShowRoot in FOptions.PaintOptions) and ((toShowTreeLines in FOptions.PaintOptions) and + (not (toHideTreeLinesIfThemed in FOptions.PaintOptions) or not (tsUseThemes in FStates))) then + begin + // Is the top node a root node? + if Run = Node then + begin + // First child gets the bottom-right bitmap if it isn't also the only child. + if IsFirstVisibleChild(FRoot, Run) then + // Is it the only child? + if IsLastVisibleChild(FRoot, Run) then + LineImage[0] := ltRight + else + LineImage[0] := ltBottomRight + else + // real last child + if IsLastVisibleChild(FRoot, Run) then + LineImage[0] := ltTopRight + else + LineImage[0] := ltTopDownRight; + end + else + begin + // No, top node is not a top level node. So we need different painting. + if HasVisibleNextSibling(Run) then + LineImage[0] := ltTopDown + else + LineImage[0] := ltNone; + end; + end; + end; + end; + + if (tsUseExplorerTheme in FStates) and HasChildren[Node] and (Indent >= 0) + and not ((vsAllChildrenHidden in Node.States) and (toAutoHideButtons in TreeOptions.AutoOptions)) then + LineImage[Indent] := ltNone; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DetermineNextCheckState(CheckType: TCheckType; CheckState: TCheckState): TCheckState; + +// Determines the next check state in case the user click the check image or pressed the space key. + +begin + case CheckType of + ctTriStateCheckBox, + ctButton, + ctCheckBox: + begin + Result := CheckState.GetToggled(); + end;//ctCheckbox + ctRadioButton: + Result := csCheckedNormal; + else + Result := csMixedNormal; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DetermineScrollDirections(X, Y: TDimension): TScrollDirections; + +// Determines which direction the client area must be scrolled depending on the given position. + +begin + Result:= []; + + if CanAutoScroll then + begin + // Calculation for wheel panning/scrolling is a bit different to normal auto scroll. + if tsPanning in FStates then + begin + if (X - FLastClickPos.X) < -8 then + Include(Result, TScrollDirection.sdLeft); + if (X - FLastClickPos.X) > 8 then + Include(Result, TScrollDirection.sdRight); + + if (Y - FLastClickPos.Y) < -8 then + Include(Result, TScrollDirection.sdUp); + if (Y - FLastClickPos.Y) > 8 then + Include(Result, TScrollDirection.sdDown); + end + else + begin + if (X < FDefaultNodeHeight) and (FEffectiveOffsetX <> 0) then + Include(Result, TScrollDirection.sdLeft); + if (ClientWidth + FEffectiveOffsetX < FRangeX) and (X > ClientWidth - FDefaultNodeHeight) then + Include(Result, TScrollDirection.sdRight); + + if (Y < FDefaultNodeHeight) and (FOffsetY <> 0) then + Include(Result, TScrollDirection.sdUp); + if (ClientHeight - FOffsetY < FRangeY) and (Y > ClientHeight - FDefaultNodeHeight) then + Include(Result, TScrollDirection.sdDown); + + // Since scrolling during dragging is not handled via the timer we do a check here whether the auto + // scroll timeout already has elapsed or not. + if (Result <> []) and + ((Assigned(FDragManager) and DragManager.IsDropTarget) or + (FindDragTarget(Point(X, Y), False) = Self)) then + begin + if FDragScrollStart = 0 then + FDragScrollStart := timeGetTime; + // Reset any scroll direction to avoid scroll in the case the user is dragging and the auto scroll time has not + // yet elapsed. + if ((Int64(timeGetTime) - FDragScrollStart) < FAutoScrollDelay) then + Result := []; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoAddToSelection(Node: PVirtualNode); +begin + if Assigned(FOnAddToSelection) then + FOnAddToSelection(Self, Node); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoAdvancedHeaderDraw(var PaintInfo: THeaderPaintInfo; const Elements: THeaderPaintElements); + +begin + if Assigned(FOnAdvancedHeaderDraw) then + FOnAdvancedHeaderDraw(FHeader, PaintInfo, Elements); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoAfterCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect); + +begin + if Assigned(FOnAfterCellPaint) then + FOnAfterCellPaint(Self, Canvas, Node, Column, CellRect); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoAfterItemErase(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect); + +begin + if Assigned(FOnAfterItemErase) then + FOnAfterItemErase(Self, Canvas, Node, ItemRect); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoAfterItemPaint(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect); + +begin + if Assigned(FOnAfterItemPaint) then + FOnAfterItemPaint(Self, Canvas, Node, ItemRect); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoAfterPaint(Canvas: TCanvas); + +begin + if Assigned(FOnAfterPaint) then + FOnAfterPaint(Self, Canvas); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoAutoScroll(X, Y: TDimension); + +begin + FScrollDirections := DetermineScrollDirections(X, Y); + + if not (tsPanning in FStates) then + begin + if FScrollDirections = [] then + begin + if ((FStates * [tsScrollPending, tsScrolling]) <> []) then + begin + StopTimer(ScrollTimer); + DoStateChange([], [tsScrollPending, tsScrolling]); + end; + end + else + begin + // start auto scroll if not yet done + if (FStates * [tsScrollPending, tsScrolling]) = [] then + begin + DoStateChange([tsScrollPending]); + SetTimer(Handle, ScrollTimer, FAutoScrollDelay, nil); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoBeforeDrag(Node: PVirtualNode; Column: TColumnIndex): Boolean; + +begin + Result := False; + if Assigned(FOnDragAllowed) then + FOnDragAllowed(Self, Node, Column, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoBeforeCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; + CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); + +var + UpdateRect: TRect; + +begin + if Assigned(FOnBeforeCellPaint) then + begin + if CellPaintMode = cpmGetContentMargin then + begin + // Prevent drawing if we are only about to get the margin. As this also clears the update rect we need to save it. + GetUpdateRect(Handle, UpdateRect, False); + SetUpdateState(True); + end; + + Canvas.Font.Assign(Self.Font); // Fixes issue #298 + FOnBeforeCellPaint(Self, Canvas, Node, Column, CellPaintMode, CellRect, ContentRect); + + if CellPaintMode = cpmGetContentMargin then + SetUpdateState(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoBeforeItemErase(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect; var Color: TColor; + var EraseAction: TItemEraseAction); + +begin + if Assigned(FOnBeforeItemErase) then + FOnBeforeItemErase(Self, Canvas, Node, ItemRect, Color, EraseAction); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoBeforeItemPaint(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect): Boolean; + +begin + // By default custom draw will not be used, so the tree handles drawing the node. + Result := False; + if Assigned(FOnBeforeItemPaint) then + FOnBeforeItemPaint(Self, Canvas, Node, ItemRect, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoBeforePaint(Canvas: TCanvas); + +begin + if Assigned(FOnBeforePaint) then + FOnBeforePaint(Self, Canvas); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoCancelEdit(): Boolean; + +// Called when the current edit action or a pending edit must be cancelled. + +begin + Result := DoEndEdit(True); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoEndEdit(pCancel: Boolean = False): Boolean; + +// Called to finish a current edit action or stop the edit timer if an edit operation is pending. +// Pass True if the edit should be cancelled, pass False if the new text should be used and saved. +// Returns True if editing was successfully ended/canceled or the control was not in edit mode. +// Returns False if the control could not leave the edit mode e.g. due to an invalid value that was entered. + +begin + StopTimer(EditTimer); + DoStateChange([], [tsEditPending]); + if not (tsEditing in FStates) then + Exit(True); + if pCancel then + Result := FEditLink.CancelEdit + else + Result := FEditLink.EndEdit; + if Result then + begin + DoStateChange([], [tsEditing]); + FEditLink := nil; + if Assigned(FOnEdited) then + FOnEdited(Self, FFocusedNode, FEditColumn); + end; + TrySetFocus(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoCanEdit(Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); + +begin + if Assigned(FOnEditing) then + FOnEditing(Self, Node, Column, Allowed); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoCanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex; + var Allowed: Boolean); + +begin + if Assigned(FOnCanSplitterResizeNode) then + FOnCanSplitterResizeNode(Self, P, Node, Column, Allowed); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoChange(Node: PVirtualNode); + +begin + StopTimer(ChangeTimer); + if Assigned(FOnChange) then + FOnChange(Self, Node); + + // This is a good place to reset the cached node. This is the same as the node passed in here. + // This is necessary to allow descendants to override this method and get the node then. + DoStateChange([], [tsChangePending]); + FLastChangedNode := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoCheckClick(Node: PVirtualNode; NewCheckState: TCheckState); + +begin + if ChangeCheckState(Node, NewCheckState) then + begin + DoChecked(Node); + if SyncCheckstateWithSelection[Node] then + begin + // selection should follow check state + if (NewCheckState = csCheckedNormal) then + Selected[node] := true + else + Selected[node] := false; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoChecked(Node: PVirtualNode); + +begin + if Assigned(FOnChecked) then + FOnChecked(Self, Node); + if (Self.UpdateCount = 0) then // See issue #1174 + NotifyAccessibleEvent(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoChecking(Node: PVirtualNode; var NewCheckState: TCheckState): Boolean; + +// Determines if a node is allowed to change its check state to NewCheckState. + +begin + if (toReadOnly in FOptions.MiscOptions) or (vsDisabled in Node.States) then + Result := False + else + begin + Result := True; + if Assigned(FOnChecking) then + FOnChecking(Self, Node, NewCheckState, Result); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoCollapsed(Node: PVirtualNode); +var + lFirstSelected: PVirtualNode; + lParent: PVirtualNode; +begin + if Assigned(FOnCollapsed) then + FOnCollapsed(Self, Node); + + if (Self.UpdateCount = 0) then // See issue #1174 + NotifyAccessibleEvent(); + + if (toAlwaysSelectNode in TreeOptions.SelectionOptions) then + begin + // Select the next visible parent if the currently selected node gets invisible due to a collapse + // This makes the VT behave more like the Win32 custom TreeView control + // This makes only sense no no multi selection is allowed and if there is a selected node at all + lFirstSelected := GetFirstSelected(); + if Assigned(lFirstSelected) and not FullyVisible[lFirstSelected] then + begin + lParent := GetVisibleParent(lFirstSelected); + Selected[lFirstSelected] := False; + Selected[lParent] := True; + end;//if + //if there is (still) no selected node, then use FNextNodeToSelect to select one + if SelectedCount = 0 then + EnsureNodeSelected(False); + end;//if +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoCollapsing(Node: PVirtualNode): Boolean; + +begin + Result := True; + if Assigned(FOnCollapsing) then + FOnCollapsing(Self, Node, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoColumnChecked(Column: TColumnIndex); +begin + if Assigned(FOnColumnChecked) then + FOnColumnChecked(Self.FHeader, Column); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoColumnChecking(Column: TColumnIndex; var NewCheckState: TCheckState): Boolean; + +// Determines if a column is allowed to change its check state to NewCheckState. + +begin + Result := True; + if Assigned(FOnColumnChecking) then + FOnColumnChecking(Self.Header, Column, NewCheckState, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoColumnClick(Column: TColumnIndex; Shift: TShiftState); + +begin + if Assigned(FOnColumnClick) then + FOnColumnClick(Self, Column, Shift); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoColumnDblClick(Column: TColumnIndex; Shift: TShiftState); + +begin + if Assigned(FOnColumnDblClick) then + FOnColumnDblClick(Self, Column, Shift); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoColumnHeaderSpanning(Column: TColumnIndex; var Count: Integer); +begin + if Assigned(FOnColumnHeaderSpanning) then + FOnColumnHeaderSpanning(Self.Header, Column, Count); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoColumnResize(Column: TColumnIndex); + +var + R: TRect; + Run: PVirtualNode; + +begin + if not (csLoading in ComponentState) and HandleAllocated then + begin + // Reset all vsHeightMeasured flags if we are in multiline mode. + Run := GetFirstInitialized; + while Assigned(Run) do + begin + if vsMultiline in Run.States then + Exclude(Run.States, vsHeightMeasured); + Run := GetNextInitialized(Run); + end; + if Header.Columns.UpdateCount = 0 then + UpdateHorizontalScrollBar(True); + if Column > NoColumn then + begin + // Invalidate client area from the current column all to the right (or left in RTL mode). + R := ClientRect; + if not (toAutoSpanColumns in FOptions.AutoOptions) then + if UseRightToLeftAlignment then + R.Right := FHeader.Columns[Column].Left + FHeader.Columns[Column].Width + ComputeRTLOffset + else + R.Left := FHeader.Columns[Column].Left; + InvalidateRect(@R, False); + FHeader.Invalidate(FHeader.Columns[Column], True); + end; + if [hsColumnWidthTracking, hsResizing] * FHeader.States = [hsColumnWidthTracking] then + UpdateWindow(); + + if not (IsUpdating) then + UpdateDesigner; // design time only + + if Assigned(FOnColumnResize) and not (hsResizing in FHeader.States) then + FOnColumnResize(FHeader, Column); + + // If the tree is currently in edit state then notify edit link. + if tsEditing in FStates then + UpdateEditBounds; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoColumnVisibilityChanged(const Column: TColumnIndex; Visible: Boolean); + // Triggers the OnColumnVisibilityChanged event. +begin + if Assigned(OnColumnVisibilityChanged) then + OnColumnVisibilityChanged(Self, Column, Visible); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoCompare(Node1, Node2: PVirtualNode; Column: TColumnIndex): Integer; + +begin + Result := 0; + if Assigned(FOnCompareNodes) then + FOnCompareNodes(Self, Node1, Node2, Column, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoCreateDataObject: IDataObject; + +begin + Result := nil; + if Assigned(FOnCreateDataObject) then + FOnCreateDataObject(Self, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoCreateDragManager: IVTDragManager; + +begin + Result := nil; + if Assigned(FOnCreateDragManager) then + FOnCreateDragManager(Self, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; + +begin + Result := nil; + if Assigned(FOnCreateEditor) then + FOnCreateEditor(Self, Node, Column, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoDragging(P: TPoint); + +// Initiates finally the drag'n drop operation and returns after DD is finished. + + //--------------- local function -------------------------------------------- + + function GetDragOperations: Integer; + + begin + if FDragOperations = [] then + Result := DROPEFFECT_COPY or DROPEFFECT_MOVE or DROPEFFECT_LINK + else + begin + Result := 0; + if doCopy in FDragOperations then + Result := Result or DROPEFFECT_COPY; + if doLink in FDragOperations then + Result := Result or DROPEFFECT_LINK; + if doMove in FDragOperations then + Result := Result or DROPEFFECT_MOVE; + end; + end; + + //--------------- end local function ---------------------------------------- + +var + AllowedEffects: Integer; + DragObject: TDragObject; + + DataObject: IDataObject; + +begin + DataObject := nil; + // Dragging is dragging, nothing else. + DoCancelEdit; + + if Assigned(FCurrentHotNode) then + begin + InvalidateNode(FCurrentHotNode); + FCurrentHotNode := nil; + end; + // Select the focused node if not already done. + if Assigned(FFocusedNode) and not (vsSelected in FFocusedNode.States) then + begin + InternalAddToSelection(FFocusedNode, False); + InvalidateNode(FFocusedNode); + end; + + UpdateWindow(); + + // Keep a list of all currently selected nodes as this list might change, + // but we have probably to delete currently selected nodes. + FDragSelection := GetSortedSelection(True); + try + DoStateChange([tsOLEDragging], [tsOLEDragPending, tsClearPending]); + + // An application might create a drag object like used during VCL dd. This is not required for OLE dd but + // required as parameter. + DragObject := nil; + DoStartDrag(DragObject); + DragObject.Free; + + DataObject := DragManager.DataObject; + PrepareDragImage(P, DataObject); + + FLastDropMode := dmOnNode; + // Don't forget to initialize the result. It might never be touched. + FLastDragEffect := DROPEFFECT_NONE; + AllowedEffects := GetDragOperations; + try + DragAndDrop(AllowedEffects, DataObject, FLastDragEffect); + DragManager.ForceDragLeave; + finally + GetCursorPos(P); + P := ScreenToClient(P); + DoEndDrag(Self, P.X, P.Y); + + // Finish the operation. + if (FLastDragEffect = DROPEFFECT_MOVE) and (toAutoDeleteMovedNodes in TreeOptions.AutoOptions) then + begin + // The operation was a move so delete the previously selected nodes. + DeleteSelectedNodes; + end; + + DoStateChange([], [tsOLEDragging]); + end; + finally + FDragSelection := nil; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoDragExpand; +begin + StopTimer(ExpandTimer); + if Assigned(FDropTargetNode) and (vsHasChildren in FDropTargetNode.States) and + not (vsExpanded in FDropTargetNode.States) then + begin + ToggleNode(FDropTargetNode); + UpdateWindow(); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoDragOver(Source: TObject; Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode; + var Effect: Integer): Boolean; + +begin + Result := False; + if Assigned(FOnDragOver) then + FOnDragOver(Self, Source, Shift, State, Pt, Mode, Effect, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoDragDrop(Source: TObject; const DataObject: TVTDragDataObject; const Formats: TFormatArray; + Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode); + +begin + if Assigned(FOnDragDrop) then + FOnDragDrop(Self, Source, DataObject, Formats, Shift, Pt, Effect, Mode); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoBeforeDrawLineImage(Node: PVirtualNode; Level: Integer; var XPos: TDimension); + +begin + if Assigned(FOnBeforeDrawLineImage) then + FOnBeforeDrawLineImage(Self, Node, Level, XPos); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoEdit; + +begin + Application.CancelHint; + StopTimer(ScrollTimer); + StopTimer(EditTimer); + DoStateChange([], [tsEditPending]); + if Assigned(FFocusedNode) and not (vsDisabled in FFocusedNode.States) and + not (toReadOnly in FOptions.MiscOptions) and (FEditLink = nil) then + begin + InternalSetFocusedColumn(FEditColumn); + ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions, not (toDisableAutoscrollOnEdit in FOptions.AutoOptions)); + FEditLink := DoCreateEditor(FFocusedNode, FEditColumn); + if Assigned(FEditLink) then + begin + DoStateChange([tsEditing], [tsDrawSelecting, tsDrawSelPending, tsToggleFocusedSelection, tsOLEDragPending, + tsOLEDragging, tsClearPending, tsDrawSelPending, tsScrollPending, tsScrolling]); + if FEditLink.PrepareEdit(Self, FFocusedNode, FEditColumn) then + begin + UpdateEditBounds; + // Node needs repaint because the selection rectangle and static text must disappear. + InvalidateNode(FFocusedNode); + if not FEditLink.BeginEdit then + DoStateChange([], [tsEditing]); + end + else + DoStateChange([], [tsEditing]); + if not (tsEditing in FStates) then + FEditLink := nil; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoEndDrag(Target: TObject; X, Y: TDimension); + +// Does some housekeeping for VCL drag'n drop; + +begin + inherited; + + DragFinished; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoEndOperation(OperationKind: TVTOperationKind); + +begin + if Assigned(FOnEndOperation) then + FOnEndOperation(Self, OperationKind); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoEnter(); +begin + inherited; + EnsureNodeSelected(False); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoExpanded(Node: PVirtualNode); + +begin + if Assigned(FOnExpanded) then + FOnExpanded(Self, Node); + if (Self.UpdateCount = 0) then // See issue #1174 + NotifyAccessibleEvent(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoExpanding(Node: PVirtualNode): Boolean; + +begin + Result := True; + if Assigned(FOnExpanding) then + FOnExpanding(Self, Node, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoFocusChange(Node: PVirtualNode; Column: TColumnIndex); + +begin + if Assigned(FOnFocusChanged) then + FOnFocusChanged(Self, Node, Column); + NotifyAccessibleEvent(EVENT_OBJECT_LOCATIONCHANGE); + NotifyAccessibleEvent(EVENT_OBJECT_NAMECHANGE); + NotifyAccessibleEvent(EVENT_OBJECT_VALUECHANGE); + NotifyAccessibleEvent(EVENT_OBJECT_STATECHANGE); + NotifyAccessibleEvent(EVENT_OBJECT_SELECTION); + NotifyAccessibleEvent(EVENT_OBJECT_FOCUS); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoFocusChanging(OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex): Boolean; + +begin + Result := (OldColumn = NewColumn) or FHeader.AllowFocus(NewColumn); + if Assigned(FOnFocusChanging) then + FOnFocusChanging(Self, OldNode, NewNode, OldColumn, NewColumn, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoFocusNode(Node: PVirtualNode; Ask: Boolean); + +begin + if not (tsEditing in FStates) or EndEditNode then + begin + if Node = FRoot then + Node := nil; + if (FFocusedNode <> Node) and (not Ask or DoFocusChanging(FFocusedNode, Node, FFocusedColumn, FFocusedColumn)) then + begin + if Assigned(FFocusedNode) then + begin + // Do automatic collapsing of last focused node if enabled. This is however only done if + // old and new focused node have a common parent node. + if (toAutoExpand in FOptions.AutoOptions) and Assigned(Node) and (Node.Parent = FFocusedNode.Parent) and + (vsExpanded in FFocusedNode.States) then + ToggleNode(FFocusedNode) + else + InvalidateNode(FFocusedNode); + end; + FFocusedNode := Node; + end; + + // Have to scroll the node into view, even it is the same node as before. + if Assigned(FFocusedNode) then + begin + // Make sure a valid column is set if columns are used and no column has currently the focus. + // We should also check if the maincolumn is allowfocus + if FHeader.UseColumns and (not FHeader.Columns.IsValidColumn(FFocusedColumn)) + and FHeader.AllowFocus(FHeader.MainColumn) then + FFocusedColumn := FHeader.MainColumn; + // Do automatic expansion of the newly focused node if enabled. + if (toAutoExpand in FOptions.AutoOptions) and not (vsExpanded in FFocusedNode.States) then + ToggleNode(FFocusedNode); + InvalidateNode(FFocusedNode); + if (FUpdateCount = 0) and not (toDisableAutoscrollOnFocus in FOptions.AutoOptions) then + ScrollIntoView(FFocusedNode, (toCenterScrollIntoView in FOptions.SelectionOptions) and + (MouseButtonDown * FStates = []), not (toFullRowSelect in FOptions.SelectionOptions) ); + end; + + // Reset range anchor if necessary. + if FSelectionCount = 0 then + ResetRangeAnchor; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoFreeNode(Node: PVirtualNode); + +var + IntfData: IInterface; +begin + // Prevent invalid references + if Node = FLastChangedNode then + FLastChangedNode := nil; + if Node = FCurrentHotNode then + FCurrentHotNode := nil; + if Node = FDropTargetNode then + FDropTargetNode := nil; + if Node = FLastStructureChangeNode then + FLastStructureChangeNode := nil; + if Node = FFocusedNode then + FFocusedNode := nil; + if Node = FNextNodeToSelect then + UpdateNextNodeToSelect(Node); + if Node = FLastHitInfo.HitNode then + FLastHitInfo.HitNode := nil; + // fire event + if Assigned(FOnFreeNode) and ([vsInitialized, vsOnFreeNodeCallRequired] * Node.States <> []) then + FOnFreeNode(Self, Node); + + if vsReleaseCallOnUserDataRequired in Node.States then + begin + // Data may have been set to nil, in which case we can't call _Release on it + IntfData := GetInterfaceFromNodeData(Node); + if Assigned(IntfData) then + IntfData._Release(); + end; + + FreeMem(Node); + if Self.UpdateCount = 0 then + EnsureNodeSelected(True); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex; + CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; + +// Determines the margins of the content rectangle caused by DoBeforeCellPaint. +// Note that shrinking the content rectangle results in positive margins whereas enlarging the content rectangle results +// in negative margins. + +var + CellRect, + ContentRect: TRect; + +begin + Result := Point(0, 0); + + if Assigned(FOnBeforeCellPaint) then // Otherwise DoBeforeCellPaint has no effect. + begin + if Canvas = nil then + Canvas := Self.Canvas; + + // Determine then node's cell rectangle and content rectangle before calling DoBeforeCellPaint. + CellRect := GetDisplayRect(Node, Column, True); + ContentRect := CellRect; + DoBeforeCellPaint(Canvas, Node, Column, cpmGetContentMargin, CellRect, ContentRect); + + // Calculate the changes caused by DoBeforeCellPaint. + case CellContentMarginType of + ccmtAllSides: + // Calculate the width difference and high difference. + Result := Point((CellRect.Right - CellRect.Left) - (ContentRect.Right - ContentRect.Left), + (CellRect.Bottom - CellRect.Top) - (ContentRect.Bottom - ContentRect.Top)); + ccmtTopLeftOnly: + // Calculate the left margin and top margin only. + Result := Point(ContentRect.Left - CellRect.Left, ContentRect.Top - CellRect.Top); + ccmtBottomRightOnly: + // Calculate the right margin and bottom margin only. + Result := Point(CellRect.Right - ContentRect.Right, CellRect.Bottom - ContentRect.Bottom); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoGetCursor(var Cursor: TCursor); + +begin + if Assigned(FOnGetCursor) then + FOnGetCursor(Self, Cursor); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoGetHeaderCursor(var Cursor: TVTCursor); + +begin + if Assigned(FOnGetHeaderCursor) then + FOnGetHeaderCursor(FHeader, Cursor); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoGetImageIndex(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; + var Ghosted: Boolean; var Index: TImageIndex): TCustomImageList; + +// Queries the application/descendant about certain image properties for a node. +// Returns a custom image list if given by the callee, otherwise nil. +const + cTVTImageKind2String: Array [TVTImageKind] of string = ('ikNormal', 'ikSelected', 'ikState', 'ikOverlay'); +begin + if (Kind = ikState) and Assigned(StateImages) then + Result := Self.StateImages + else + Result := Self.Images; + // First try the enhanced event to allow for custom image lists. + if Assigned(FOnGetImageEx) then + FOnGetImageEx(Self, Node, Kind, Column, Ghosted, Index, Result) + else if Assigned(FOnGetImage) then + FOnGetImage(Self, Node, Kind, Column, Ghosted, Index); + + Assert((Index < 0) or Assigned(Result), 'An image index was supplied for TVTImageKind.' + cTVTImageKind2String[Kind] + ' but no image list was supplied.'); + if not Assigned(Result) then + Index := -1; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoGetImageText(Node: PVirtualNode; Kind: TVTImageKind; + Column: TColumnIndex; var ImageText: string); + +// Queries the application/descendant about alternative image text for a node. + +begin + if Assigned(FOnGetImageText) then + FOnGetImageText(Self, Node, Kind, Column, ImageText); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoGetLineStyle(var Bits: Pointer); + +begin + if Assigned(FOnGetLineStyle) then + FOnGetLineStyle(Self, Bits); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex; + var LineBreakStyle: TVTTooltipLineBreakStyle): string; + +begin + Result := Hint; + LineBreakStyle := hlbDefault; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex; + var LineBreakStyle: TVTTooltipLineBreakStyle): string; + +begin + Result := Hint; + LineBreakStyle := hlbDefault; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoGetNodeExtraWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): TDimension; + +// Returns the pixel width of extra space occupied by node contents (for example, static text). + +begin + Result := 0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): TDimension; + +// Returns the pixel width of a node. + +begin + Result := 0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoGetPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Position: TPoint): TPopupMenu; + +// Queries the application whether there is a node specific popup menu. + +var + Run: PVirtualNode; + AskParent: Boolean; + +begin + Result := nil; + if Assigned(FOnGetPopupMenu) then + begin + Run := Node; + + if Assigned(Run) then + begin + AskParent := True; + repeat + FOnGetPopupMenu(Self, Run, Column, Position, AskParent, Result); + Run := Run.Parent; + until (Run = FRoot) or Assigned(Result) or not AskParent; + end + else + FOnGetPopupMenu(Self, nil, NoColumn, Position, AskParent, Result); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoGetUserClipboardFormats(var Formats: TFormatEtcArray); + +begin + if Assigned(FOnGetUserClipboardFormats) then + FOnGetUserClipboardFormats(Self, Formats); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHeaderAddPopupItem(const Column: TColumnIndex; var Cmd: TAddPopupItemType); + +begin + if Assigned(FOnHeaderAddPopupItem) then + FOnHeaderAddPopupItem(Self, Column, Cmd); + +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHeaderClick(const HitInfo: TVTHeaderHitInfo); + +begin + if Assigned(FOnHeaderClick) then + FOnHeaderClick(FHeader, HitInfo); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHeaderDblClick(const HitInfo: TVTHeaderHitInfo); + +begin + if Assigned(FOnHeaderDblClick) then + FOnHeaderDblClick(FHeader, HitInfo); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHeaderDragged(Column: TColumnIndex; OldPosition: TColumnPosition); + +begin + if Assigned(FOnHeaderDragged) then + FOnHeaderDragged(FHeader, Column, OldPosition); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHeaderDraggedOut(Column: TColumnIndex; DropPosition: TPoint); + +begin + if Assigned(FOnHeaderDraggedOut) then + FOnHeaderDraggedOut(FHeader, Column, DropPosition); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoHeaderDragging(Column: TColumnIndex): Boolean; + +begin + Result := True; + if Assigned(FOnHeaderDragging) then + FOnHeaderDragging(FHeader, Column, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHeaderDraw(Canvas: TCanvas; Column: TVirtualTreeColumn; R: TRect; Hover, Pressed: Boolean; + DropMark: TVTDropMarkMode); + +begin + if Assigned(FOnHeaderDraw) then + FOnHeaderDraw(FHeader, Canvas, Column, R, Hover, Pressed, DropMark); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHeaderDrawQueryElements(var PaintInfo: THeaderPaintInfo; var Elements: THeaderPaintElements); + +begin + if Assigned(FOnHeaderDrawQueryElements) then + FOnHeaderDrawQueryElements(FHeader, PaintInfo, Elements); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHeaderMouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: TDimension); + +begin + if Assigned(FOnHeaderMouseDown) then + FOnHeaderMouseDown(FHeader, Button, Shift, X, Y); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHeaderMouseMove(Shift: TShiftState; X, Y: TDimension); + +begin + if Assigned(FOnHeaderMouseMove) then + FOnHeaderMouseMove(FHeader, Shift, X, Y); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHeaderMouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: TDimension); + +begin + if Assigned(FOnHeaderMouseUp) then + FOnHeaderMouseUp(FHeader, Button, Shift, X, Y); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoHotChange(Old, New: PVirtualNode); + +begin + if Assigned(FOnHotChange) then + FOnHotChange(Self, Old, New); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoIncrementalSearch(Node: PVirtualNode; const Text: string): Integer; + +begin + Result := 0; + if Assigned(FOnIncrementalSearch) then + FOnIncrementalSearch(Self, Node, Text, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoInitChildren(Node: PVirtualNode; var ChildCount: Cardinal): Boolean; +/// The function calls the OnInitChildren and returns True if the event was called; it returns False if the caller can expect that no changes have been made to ChildCount +begin + if Assigned(FOnInitChildren) then + begin + FOnInitChildren(Self, Node, ChildCount); + Result := True; + end + else + Result := False; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoInitNode(Parent, Node: PVirtualNode; var InitStates: TVirtualNodeInitStates); + +begin + if Assigned(FOnInitNode) then + FOnInitNode(Self, Parent, Node, InitStates); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoKeyAction(var CharCode: Word; var Shift: TShiftState): Boolean; + +begin + Result := True; + if Assigned(FOnKeyAction) then + FOnKeyAction(Self, CharCode, Shift, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoLoadUserData(Node: PVirtualNode; Stream: TStream); + +begin + if Assigned(FOnLoadNode) then + if Node = FRoot then + FOnLoadNode(Self, nil, Stream) + else + FOnLoadNode(Self, Node, Stream); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoMeasureItem(TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: TDimension); + +begin + if not (vsInitialized in Node.States) then + InitNode(Node); + if Assigned(FOnMeasureItem) then + FOnMeasureItem(Self, TargetCanvas, Node, NodeHeight); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoMouseEnter(); + +begin + if Assigned(FOnMouseEnter) then + FOnMouseEnter(Self); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoMouseLeave; + +begin + if Assigned(FOnMouseLeave) then + FOnMouseLeave(Self); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoNodeCopied(Node: PVirtualNode); + +begin + if Assigned(FOnNodeCopied) then + FOnNodeCopied(Self, Node); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoNodeCopying(Node, NewParent: PVirtualNode): Boolean; + +begin + Result := True; + if Assigned(FOnNodeCopying) then + FOnNodeCopying(Self, Node, NewParent, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoNodeClick(const HitInfo: THitInfo); + +begin + if Assigned(FOnNodeClick) then + FOnNodeClick(Self, HitInfo); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoNodeDblClick(const HitInfo: THitInfo); + +begin + if Assigned(FOnNodeDblClick) then + FOnNodeDblClick(Self, HitInfo); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoNodeHeightDblClickResize(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState; + P: TPoint): Boolean; + +begin + Result := True; + if Assigned(FOnNodeHeightDblClickResize) then + FOnNodeHeightDblClickResize(Self, Node, Column, Shift, P, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoNodeHeightTracking(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState; + var TrackPoint: TPoint; P: TPoint): Boolean; + +begin + Result := True; + if Assigned(FOnNodeHeightTracking) then + FOnNodeHeightTracking(Self, Node, Column, Shift, TrackPoint, P, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoNodeMoved(Node: PVirtualNode); + +begin + if Assigned(FOnNodeMoved) then + FOnNodeMoved(Self, Node); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoNodeMoving(Node, NewParent: PVirtualNode): Boolean; + +begin + Result := True; + if Assigned(FOnNodeMoving) then + FOnNodeMoving(Self, Node, NewParent, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoPaintBackground(Canvas: TCanvas; R: TRect): Boolean; + +begin + Result := False; + if Assigned(FOnPaintBackground) then + FOnPaintBackground(Self, Canvas, R, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoPaintDropMark(Canvas: TCanvas; Node: PVirtualNode; R: TRect); + +// draws the drop mark into the given rectangle +// Note: Changed properties of the given canvas should be reset to their previous values. + +var + SaveBrushColor: TColor; + SavePenStyle: TPenStyle; + +begin + if FLastDropMode in [dmAbove, dmBelow] then + with Canvas do + begin + SavePenStyle := Pen.Style; + Pen.Style := psClear; + SaveBrushColor := Brush.Color; + Brush.Color := FColors.DropMarkColor; + + if FLastDropMode = dmAbove then + begin + Polygon([Point(R.Left + 2, R.Top), + Point(R.Right - 2, R.Top), + Point(R.Right - 2, R.Top + 6), + Point(R.Right - 6, R.Top + 2), + Point(R.Left + 6 , R.Top + 2), + Point(R.Left + 2, R.Top + 6) + ]); + end + else + Polygon([Point(R.Left + 2, R.Bottom - 1), + Point(R.Right - 2, R.Bottom - 1), + Point(R.Right - 2, R.Bottom - 8), + Point(R.Right - 7, R.Bottom - 3), + Point(R.Left + 7 , R.Bottom - 3), + Point(R.Left + 2, R.Bottom - 8) + ]); + Brush.Color := SaveBrushColor; + Pen.Style := SavePenStyle; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoPaintNode(var PaintInfo: TVTPaintInfo); + +begin +end; + +procedure TBaseVirtualTree.DoPaintText(Node: PVirtualNode; const Canvas: TCanvas; Column: TColumnIndex; TextType: TVSTTextType); +begin + if Assigned(FOnPaintText) then + FOnPaintText(Self, Canvas, Node, Column, TextType); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Position: TPoint); + +// Support for node dependent popup menus. + +var + Menu: TPopupMenu; + +begin + Menu := DoGetPopupMenu(Node, Column, Position); + + if Assigned(Menu) then + begin + DoStateChange([tsPopupMenuShown]); + StopTimer(EditTimer); + Menu.PopupComponent := Self; + with ClientToScreen(Position) do + Menu.Popup(X, Y); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoRemoveFromSelection(Node: PVirtualNode); + +begin + if Assigned(FOnRemoveFromSelection) then + FOnRemoveFromSelection(Self, Node); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoReset(Node: PVirtualNode); + +begin + if Assigned(FOnResetNode) then + FOnResetNode(Self, Node); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoSaveUserData(Node: PVirtualNode; Stream: TStream); + +begin + if Assigned(FOnSaveNode) then + if Node = FRoot then + FOnSaveNode(Self, nil, Stream) + else + FOnSaveNode(Self, Node, Stream); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoScroll(DeltaX, DeltaY: TDimension); + +begin + if Assigned(FOnScroll) then + FOnScroll(Self, DeltaX, DeltaY); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoSetOffsetXY(Value: TPoint; Options: TScrollUpdateOptions; ClipRect: PRect = nil): Boolean; + +// Actual offset setter used to scroll the client area, update scroll bars and invalidating the header (all optional). +// Returns True if the offset really changed otherwise False is returned. + +var + DeltaX: TDimension; + DeltaY: TDimension; + DWPStructure: HDWP; + I: Integer; + P: TPoint; + R: TRect; + +begin + // Range check, order is important here. + if Value.X < (ClientWidth - FRangeX) then + Value.X := ClientWidth - FRangeX; + if Value.X > 0 then + Value.X := 0; + DeltaX := Value.X - FOffsetX; + if UseRightToLeftAlignment then + DeltaX := -DeltaX; + if Value.Y < (ClientHeight - FRangeY) then + Value.Y := ClientHeight - FRangeY; + if Value.Y > 0 then + Value.Y := 0; + DeltaY := Value.Y - FOffsetY; + + Result := (DeltaX <> 0) or (DeltaY <> 0); + if Result then + begin + FOffsetX := Value.X; + FOffsetY := Value.Y; + Result := True; + + if tsHint in Self.FStates then + Application.CancelHint; + if FUpdateCount = 0 then + begin + // The drag image from VCL controls need special consideration. + if tsVCLDragging in FStates then + ImageList_DragShowNolock(False); + + if (suoScrollClientArea in Options) and not (tsToggling in FStates) then + begin + // Have to invalidate the entire window if there's a background. + if (toShowBackground in FOptions.PaintOptions) and Assigned(FBackground.Graphic) then + begin + // Since we don't use ScrollWindow here we have to move all client windows ourselves. + DWPStructure := BeginDeferWindowPos(ControlCount); + for I := 0 to ControlCount - 1 do + if Controls[I] is TWinControl then + begin + with Controls[I] as TWinControl do + DWPStructure := DeferWindowPos(DWPStructure, Handle, 0, Left + DeltaX, Top + DeltaY, 0, 0, + SWP_NOZORDER or SWP_NOACTIVATE or SWP_NOSIZE); + if DWPStructure = 0 then + Break; + end; + if DWPStructure <> 0 then + EndDeferWindowPos(DWPStructure); + InvalidateRect(nil, False); + end + else + begin + if (DeltaX <> 0) and (Header.Columns.GetVisibleFixedWidth > 0) then + begin + // When fixed columns exists we have to scroll separately horizontally and vertically. + // Horizontally is scroll only the client area not occupied by fixed columns and + // vertically entire client area (or clipping area if one exists). + R := ClientRect; + R.Left := Header.Columns.GetVisibleFixedWidth; + + ScrollWindow(Handle, DeltaX, 0, @R, @R); + if DeltaY <> 0 then + ScrollWindow(Handle, 0, DeltaY, ClipRect, ClipRect); + end + else + ScrollWindow(Handle, DeltaX, DeltaY, ClipRect, ClipRect); + end; + end; + + if suoUpdateNCArea in Options then + begin + if DeltaX <> 0 then + begin + UpdateHorizontalScrollBar(suoRepaintScrollBars in Options); + if (suoRepaintHeader in Options) and (hoVisible in FHeader.Options) then + FHeader.Invalidate(nil); + if not (tsSizing in FStates) and (FScrollBarOptions.ScrollBars in [System.UITypes.TScrollStyle.ssHorizontal, System.UITypes.TScrollStyle.ssBoth]) then + UpdateVerticalScrollBar(suoRepaintScrollBars in Options); + end; + + if (DeltaY <> 0) and ([tsThumbTracking, tsSizing] * FStates = []) then + begin + UpdateVerticalScrollBar(suoRepaintScrollBars in Options); + if not (FHeader.UseColumns or IsMouseSelecting) and + (FScrollBarOptions.ScrollBars in [System.UITypes.TScrollStyle.ssHorizontal, System.UITypes.TScrollStyle.ssBoth]) then + UpdateHorizontalScrollBar(suoRepaintScrollBars in Options); + end; + end; + + if tsVCLDragging in FStates then + ImageList_DragShowNolock(True); + end; + + // Finally update "hot" node if hot tracking is activated + GetCursorPos(P); + P := ScreenToClient(P); + if PtInRect(ClientRect, P) then + HandleHotTrack(P.X, P.Y); + + DoScroll(DeltaX, DeltaY); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoShowScrollBar(Bar: Integer; Show: Boolean); + +begin + ShowScrollBar(Bar, Show); + + if Assigned(FOnShowScrollBar) then + FOnShowScrollBar(Self, Bar, Show); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoStartDrag(var DragObject: TDragObject); + +begin + inherited; + + // Check if the application created an own drag object. This is needed to pass the correct source in + // OnDragOver and OnDragDrop. + if Assigned(DragObject) then + DoStateChange([tsUserDragObject]); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoStartOperation(OperationKind: TVTOperationKind); + +begin + if Assigned(FOnStartOperation) then + FOnStartOperation(Self, OperationKind); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoStateChange(Enter: TVirtualTreeStates; Leave: TVirtualTreeStates = []); + +var + ActualEnter, + ActualLeave: TVirtualTreeStates; + +begin + if Assigned(FOnStateChange) then + begin + ActualEnter := Enter - FStates; + ActualLeave := FStates * Leave; + if (ActualEnter + ActualLeave) <> [] then + FOnStateChange(Self, Enter, Leave); + end; + FStates := FStates + Enter - Leave; + Assert(FStates * [tsUseCache, tsValidationNeeded] <> [tsUseCache, tsValidationNeeded], 'Invalid state. tsUseCache and tsValidationNeeded are mutually exclusive and must not be set at the same time'); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoStructureChange(Node: PVirtualNode; Reason: TChangeReason); + +begin + StopTimer(StructureChangeTimer); + if Assigned(FOnStructureChange) then + FOnStructureChange(Self, Node, Reason); + + // This is a good place to reset the cached node and reason. These are the same as the values passed in here. + // This is necessary to allow descendants to override this method and get them. + DoStateChange([], [tsStructureChangePending]); + FLastStructureChangeNode := nil; + FLastStructureChangeReason := crIgnore; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoTimerScroll; + +var + P, + ClientP: TPoint; + InRect, + Panning: Boolean; + R, + ClipRect: TRect; + DeltaX, + DeltaY: Integer; + +begin + GetCursorPos(P); + R := ClientRect; + ClipRect := R; + MapWindowPoints(Handle, 0, R, 2); + InRect := PtInRect(R, P); + ClientP := ScreenToClient(P); + Panning := tsPanning in FStates; + + if IsMouseSelecting or InRect or Panning then + begin + DeltaX := 0; + DeltaY := 0; + if sdUp in FScrollDirections then + begin + if Panning then + DeltaY := FLastClickPos.Y - ClientP.Y - 8 + else + if InRect then + DeltaY := Min(FScrollBarOptions.VerticalIncrement, ClientHeight) + else + DeltaY := Min(FScrollBarOptions.VerticalIncrement, ClientHeight) * Abs(R.Top - P.Y); + if FOffsetY = 0 then + Exclude(FScrollDirections, sdUp); + end; + + if sdDown in FScrollDirections then + begin + if Panning then + DeltaY := FLastClickPos.Y - ClientP.Y + 8 + else + if InRect then + DeltaY := -Min(FScrollBarOptions.VerticalIncrement, ClientHeight) + else + DeltaY := -Min(FScrollBarOptions.VerticalIncrement, ClientHeight) * Abs(P.Y - R.Bottom); + if (ClientHeight - FOffsetY) = FRangeY then + Exclude(FScrollDirections, sdDown); + end; + + if sdLeft in FScrollDirections then + begin + if Panning then + DeltaX := FLastClickPos.X - ClientP.X - 8 + else + if InRect then + DeltaX := FScrollBarOptions.HorizontalIncrement + else + DeltaX := FScrollBarOptions.HorizontalIncrement * Abs(R.Left - P.X); + if FEffectiveOffsetX = 0 then + Exclude(FScrollDirections, sdleft); + end; + + if sdRight in FScrollDirections then + begin + if Panning then + DeltaX := FLastClickPos.X - ClientP.X + 8 + else + if InRect then + DeltaX := -FScrollBarOptions.HorizontalIncrement + else + DeltaX := -FScrollBarOptions.HorizontalIncrement * Abs(P.X - R.Right); + + if (ClientWidth + FEffectiveOffsetX) = FRangeX then + Exclude(FScrollDirections, sdRight); + end; + + if UseRightToLeftAlignment then + DeltaX := - DeltaX; + + if IsMouseSelecting then + begin + // In order to avoid scrolling the area which needs a repaint due to the changed selection rectangle + // we limit the scroll area explicitely. + OffsetRect(ClipRect, DeltaX, DeltaY); + DoSetOffsetXY(Point(FOffsetX + DeltaX, FOffsetY + DeltaY), DefaultScrollUpdateFlags, @ClipRect); + // When selecting with the mouse then either update only the parts of the window which have been uncovered + // by the scroll operation if no change in the selection happend or invalidate and redraw the entire + // client area otherwise (to avoid the time consuming task of determining the display rectangles of every + // changed node). + if CalculateSelectionRect(ClientP.X, ClientP.Y) and HandleDrawSelection(ClientP.X, ClientP.Y) then + InvalidateRect(nil, False) + else + begin + // The selection did not change so invalidate only the part of the window which really needs an update. + // 1) Invalidate the parts uncovered by the scroll operation. Add another offset range, we have to + // scroll only one stripe but have to update two. + OffsetRect(ClipRect, DeltaX, DeltaY); + SubtractRect(ClipRect, ClientRect, ClipRect); + InvalidateRect(@ClipRect, False); + + // 2) Invalidate the selection rectangles. + UnionRect(ClipRect, OrderRect(FNewSelRect), OrderRect(FLastSelRect)); + OffsetRect(ClipRect, FOffsetX, FOffsetY); + InvalidateRect(@ClipRect, False); + end; + end + else + begin + // Scroll only if there is no drag'n drop in progress. Drag'n drop scrolling is handled in DragOver. + if ((FDragManager = nil) or not DragManager.IsDropTarget) and ((DeltaX <> 0) or (DeltaY <> 0)) then + DoSetOffsetXY(Point(FOffsetX + DeltaX, FOffsetY + DeltaY), DefaultScrollUpdateFlags, nil); + end; + UpdateWindow(); + + if (FScrollDirections = []) and not (tsPanning in FStates) then + begin + StopTimer(ScrollTimer); + DoStateChange([], [tsScrollPending, tsScrolling]); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoUpdating(State: TVTUpdateState); + +begin + if Assigned(FOnUpdating) then + FOnUpdating(Self, State); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DoValidateCache(): Boolean; + +// This method fills the cache, which is used to speed up searching for nodes. +// The strategy is simple: Take the current number of visible nodes and distribute evenly a number of marks +// (which are stored in FPositionCache) so that iterating through the tree doesn't cost too much time. +// If there are less than 'CacheThreshold' nodes in the tree then the cache remains empty. +// Result is True if the cache was filled without interruption, otherwise False. +// Note: You can adjust the maximum number of nodes between two cache entries by changing CacheThreshold. + +var + EntryCount, + Index: Cardinal; + CurrentNode, + Temp: PVirtualNode; + CurrentTop: TNodeHeight; +begin + EntryCount := 0; + if not (tsStopValidation in FStates) then + begin + if FStartIndex = 0 then + FPositionCache := nil; + + EntryCount := CalculateCacheEntryCount; + SetLength(FPositionCache, EntryCount); + if FStartIndex > EntryCount then + FStartIndex := EntryCount; + + // Optimize validation by starting with FStartIndex if set. + if (FStartIndex > 0) and Assigned(FPositionCache[FStartIndex - 1].Node) then + begin + // Index is the current entry in FPositionCache. + Index := FStartIndex - 1; + // Running term for absolute top value. + CurrentTop := FPositionCache[Index].AbsoluteTop; + // Running node pointer. + CurrentNode := FPositionCache[Index].Node; + end + else + begin + // Index is the current entry in FPositionCache. + Index := 0; + // Running term for absolute top value. + CurrentTop := 0; + // Running node pointer. + CurrentNode := GetFirstVisibleNoInit(nil, True); + end; + + // EntryCount serves as counter for processed nodes here. This value can always start at 0 as + // the validation either starts also at index 0 or an index which is always a multiple of CacheThreshold + // and EntryCount is only used with modulo CacheThreshold. + EntryCount := 0; + if Assigned(CurrentNode) then + begin + while not (tsStopValidation in FStates) do + begin + // If the cache is full then stop the loop. + if (Integer(Index) >= Length(FPositionCache)) then + Break; + if (EntryCount mod CacheThreshold) = 0 then + begin + // New cache entry to set up. + with FPositionCache[Index] do + begin + Node := CurrentNode; // 2 EAccessViolation seen here in TreeSize V4.3.1, 1 in V4.4.0 (Write of address 00000000) + AbsoluteTop := CurrentTop; + end; + System.Inc(Index); + end; + + Inc(CurrentTop, NodeHeight[CurrentNode]); + // Advance to next visible node. + Temp := GetNextVisibleNoInit(CurrentNode, True); + // If there is no further node then stop the loop. + if (Temp = nil) then // CHANGED: 17.09.2013 - Veit Zimmermann + Break; // CHANGED: 17.09.2013 - Veit Zimmermann + + CurrentNode := Temp; + System.Inc(EntryCount); + end; + end; + // Finalize the position cache so no nil entry remains there. + if not (tsStopValidation in FStates) and (Integer(Index) <= High(FPositionCache)) then + begin + SetLength(FPositionCache, Index + 1); + with FPositionCache[Index] do + begin + Node := CurrentNode; + AbsoluteTop := CurrentTop; + end; + end; + end; + + Result := (EntryCount > 0) and not (tsStopValidation in FStates); + + // In variable node height mode it might have happend that some or all of the nodes have been adjusted in their + // height. During validation updates of the scrollbars is disabled so let's do this here. + if Result and (toVariableNodeHeight in FOptions.MiscOptions) then + begin + TThread.Queue(nil, procedure begin UpdateScrollBars(True) end); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DragAndDrop(AllowedEffects: Dword; const DataObject: TVTDragDataObject; var DragEffect: Integer); +var + lDragEffect: DWord; // required for type compatibility with SHDoDragDrop +begin + lDragEffect := DWord(DragEffect); + SHDoDragDrop(Self.Handle, DataObject, nil, AllowedEffects, lDragEffect); // supports drag hints on Windows Vista and later + DragEffect := Integer(lDragEffect); + end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DragCanceled; + +// Does some housekeeping for VCL drag'n drop; + +begin + inherited; + + DragFinished; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DragDrop(const DataObject: TVTDragDataObject; KeyState: Integer; Pt: TPoint; + var Effect: Integer): HResult; + +var + Shift: TShiftState; + EnumFormat: IEnumFormatEtc; + Fetched: Integer; + OLEFormat: TFormatEtc; + Formats: TFormatArray; + +begin + StopTimer(ExpandTimer); + StopTimer(ScrollTimer); + DoStateChange([], [tsScrollPending, tsScrolling]); + Formats := nil; + + // Ask explicitly again whether the action is allowed. Otherwise we may accept a drop which is intentionally not + // allowed but cannot be prevented by the application because when the tree was scrolling while dropping + // no DragOver event is created by the OLE subsystem. + Result := DragOver(DragManager.DragSource, KeyState, dsDragMove, Pt, Effect); + try + if (Result <> NOERROR) or ((Effect and not DROPEFFECT_SCROLL) = DROPEFFECT_NONE) then + Result := E_FAIL + else + begin + try + Shift := KeysToShiftState(KeyState); + if tsRightButtonDown in FStates then + Include(Shift, ssRight) + else if tsMiddleButtonDown in FStates then + Include(Shift, ssMiddle) + else + Include(Shift, ssLeft); + Pt := ScreenToClient(Pt); + // Determine which formats we can get and pass them along with the data object to the drop handler. + Result := DataObject.EnumFormatEtc(DATADIR_GET, EnumFormat); + if Failed(Result) then + Abort; + Result := EnumFormat.Reset; + if Failed(Result) then + Abort; + // create a list of available formats + while EnumFormat.Next(1, OLEFormat, @Fetched) = S_OK do + begin + SetLength(Formats, Length(Formats) + 1); + Formats[High(Formats)] := OLEFormat.cfFormat; + end; + DoDragDrop(DragManager.DragSource, DataObject, Formats, Shift, Pt, Effect, FLastDropMode); + except + // An unhandled exception here leaks memory. + Application.HandleException(Self); + Result := E_UNEXPECTED; + end; + end; + finally + if Assigned(FDropTargetNode) then + begin + InvalidateNode(FDropTargetNode); + FDropTargetNode := nil; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DragEnter(KeyState: Integer; Pt: TPoint; var Effect: Integer): HResult; + +// callback routine for the drop target interface + +var + Shift: TShiftState; + Accept: Boolean; + R: TRect; + HitInfo: THitInfo; + +begin + try + if not (toAcceptOLEDrop in TreeOptions.MiscOptions) then + begin + Effect := DROPEFFECT_NONE; + Exit(NOERROR); + end; + + // Determine acceptance of drag operation and reset scroll start time. + FDragScrollStart := 0; + + Shift := KeysToShiftState(KeyState); + if tsLeftButtonDown in FStates then + Include(Shift, ssLeft); + if tsMiddleButtonDown in FStates then + Include(Shift, ssMiddle); + if tsRightButtonDown in FStates then + Include(Shift, ssRight); + Pt := ScreenToClient(Pt); + Effect := SuggestDropEffect(DragManager.DragSource, Shift, Pt, Effect); + Accept := DoDragOver(DragManager.DragSource, Shift, dsDragEnter, Pt, FLastDropMode, Effect); + if not Accept then + Effect := DROPEFFECT_NONE + else + begin + // Set initial drop target node and drop mode. + GetHitTestInfoAt(Pt.X, Pt.Y, True, HitInfo, Shift); + if Assigned(HitInfo.HitNode) then + begin + FDropTargetNode := HitInfo.HitNode; + R := GetDisplayRect(HitInfo.HitNode, FHeader.MainColumn, False); + //VSOFT CHANGE - changed back to 4.8.5 behaviour + if (hiOnItemLabel in HitInfo.HitPositions) or ((hiOnItem in HitInfo.HitPositions) and + ((toFullRowDrag in FOptions.MiscOptions){ or (toFullRowSelect in FOptions.SelectionOptions)}))then + FLastDropMode := dmOnNode + else + if ((R.Top + R.Bottom) div 2) > Pt.Y then + FLastDropMode := dmAbove + else + FLastDropMode := dmBelow; + end + else + FLastDropMode := dmNowhere; + end; + Result := NOERROR; + except + Result := E_UNEXPECTED; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DragFinished; + +// Called by DragCancelled or EndDrag to make up for the still missing mouse button up messages. +// These are important for such important things like popup menus. + +var + P: TPoint; + +begin + if [tsOLEDragging, tsVCLDragPending, tsVCLDragging, tsVCLDragFinished] * FStates = [] then + Exit; + + DoStateChange([], [tsVCLDragPending, tsVCLDragging, tsUserDragObject, tsVCLDragFinished]); + + GetCursorPos(P); + P := ScreenToClient(P); + if tsRightButtonDown in FStates then + Perform(WM_RBUTTONUP, 0, LPARAM(Integer(PointToSmallPoint(P)))) + else + if tsMiddleButtonDown in FStates then + Perform(WM_MBUTTONUP, 0, LPARAM(Integer(PointToSmallPoint(P)))) + else + Perform(WM_LBUTTONUP, 0, LPARAM(Integer(PointToSmallPoint(P)))); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DragLeave; + +var + Effect: Integer; + +begin + StopTimer(ExpandTimer); + + if Assigned(FDropTargetNode) then + begin + InvalidateNode(FDropTargetNode); + FDropTargetNode := nil; + end; + + UpdateWindow(); + + Effect := 0; + DoDragOver(nil, [], TDragState.dsDragLeave, Point(0, 0), FLastDropMode, Effect); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.DragOver(Source: TObject; KeyState: Integer; DragState: TDragState; Pt: TPoint; + var Effect: Integer): HResult; + +// callback routine for the drop target interface + +var + Shift: TShiftState; + Accept, + WindowScrolled: Boolean; + OldR, R: TRect; + NewDropMode: TDropMode; + HitInfo: THitInfo; + DragPos: TPoint; + LastNode: PVirtualNode; + DeltaX, + DeltaY: TDimension; + ScrollOptions: TScrollUpdateOptions; + +begin + ScrollOptions := DefaultScrollUpdateFlags; + + try + DragPos := Pt; + Pt := ScreenToClient(Pt); + + // Check if we have to scroll the client area. + FScrollDirections := DetermineScrollDirections(Pt.X, Pt.Y); + DeltaX := 0; + DeltaY := 0; + if FScrollDirections <> [] then + begin + // Determine amount to scroll. + if sdUp in FScrollDirections then + begin + DeltaY := Min(FScrollBarOptions.VerticalIncrement, ClientHeight); + if FOffsetY = 0 then + Exclude(FScrollDirections, sdUp); + end; + if sdDown in FScrollDirections then + begin + DeltaY := -Min(FScrollBarOptions.VerticalIncrement, ClientHeight); + if (ClientHeight - FOffsetY) = FRangeY then + Exclude(FScrollDirections, sdDown); + end; + if sdLeft in FScrollDirections then + begin + DeltaX := FScrollBarOptions.HorizontalIncrement; + if FEffectiveOffsetX = 0 then + Exclude(FScrollDirections, sdleft); + end; + if sdRight in FScrollDirections then + begin + DeltaX := -FScrollBarOptions.HorizontalIncrement; + if (ClientWidth + FEffectiveOffsetX) = FRangeX then + Exclude(FScrollDirections, sdRight); + end; + WindowScrolled := DoSetOffsetXY(Point(FOffsetX + DeltaX, FOffsetY + DeltaY), ScrollOptions, nil); + end + else + WindowScrolled := False; + + // Determine acceptance of drag operation as well as drag target. + Shift := KeysToShiftState(KeyState); + if tsLeftButtonDown in FStates then + Include(Shift, ssLeft); + if tsMiddleButtonDown in FStates then + Include(Shift, ssMiddle); + if tsRightButtonDown in FStates then + Include(Shift, ssRight); + GetHitTestInfoAt(Pt.X, Pt.Y, True, HitInfo, Shift); + + if Assigned(HitInfo.HitNode) then + R := GetDisplayRect(HitInfo.HitNode, NoColumn, False) + else + R := Rect(0, 0, 0, 0); + NewDropMode := DetermineDropMode(Pt, HitInfo, R); + + if (HitInfo.HitNode <> FDropTargetNode) or (FLastDropMode <> NewDropMode) then + begin + // Something in the tree will change. This requires to update the screen and/or the drag image. + FLastDropMode := NewDropMode; + if HitInfo.HitNode <> FDropTargetNode then + begin + StopTimer(ExpandTimer); + // The last target node is needed for the rectangle determination but must already be set for + // the recapture call, hence it must be stored somewhere. + LastNode := FDropTargetNode; + FDropTargetNode := HitInfo.HitNode; + // In order to show a selection rectangle a column must be focused. + if FFocusedColumn <= NoColumn then + FFocusedColumn := FHeader.MainColumn; + + if Assigned(LastNode) and Assigned(FDropTargetNode) then + begin + // Optimize the case that the selection moved between two nodes. + OldR := GetDisplayRect(LastNode, NoColumn, False); + UnionRect(R, R, OldR); + InvalidateRect(@R, False); + end + else + begin + if Assigned(LastNode) then + begin + // Repaint last target node. + OldR := GetDisplayRect(LastNode, NoColumn, False); + InvalidateRect(@OldR, False); + end + else + InvalidateRect(@R, False); + end; + + // Start auto expand timer if necessary. + if (toAutoDropExpand in FOptions.AutoOptions) and Assigned(FDropTargetNode) and + (vsHasChildren in FDropTargetNode.States) then + SetTimer(Handle, ExpandTimer, FAutoExpandDelay, nil); + end + else + begin + InvalidateRect(@R, False); + end; + end; + + Update; + + Effect := SuggestDropEffect(Source, Shift, Pt, Effect); + Accept := DoDragOver(Source, Shift, DragState, Pt, FLastDropMode, Effect); + if not Accept then + Effect := DROPEFFECT_NONE; + if WindowScrolled then + Effect := Effect or Integer(DROPEFFECT_SCROLL); + Result := NOERROR; + except + Result := E_UNEXPECTED; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DrawDottedHLine(const PaintInfo: TVTPaintInfo; Left, Right, Top: TDimension); +// Draws a horizontal line with alternating pixels +var + R: TRect; +begin + R := Rect(Min(Left, Right), Top, Max(Left, Right) + 1, Top + 1); + PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor; + Winapi.Windows.FillRect(PaintInfo.Canvas.Handle, R, DottedBrushTreeLines.Handle); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DrawDottedVLine(const PaintInfo: TVTPaintInfo; Top, Bottom, Left: TDimension); +// Draws a vertical line with alternating pixels +var + R: TRect; +begin + R := Rect(Left, Min(Top, Bottom), Left + 1, Max(Top, Bottom) + 1); + PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor; + Winapi.Windows.FillRect(PaintInfo.Canvas.Handle, R, DottedBrushTreeLines.Handle); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DrawGridLine(Canvas: TCanvas; R: TRect); +begin + Canvas.Brush.Color := FColors.GridLineColor; + Canvas.Brush.Style := bsSolid; + Canvas.FillRect(R); + //StyleServices.DrawElement(Canvas.Handle, StyleServices.GetElementDetails(tlGroupHeaderLineOpenSelectedNotFocused), R {$IF CompilerVersion >= 34}, @R, CurrentPPI{$IFEND}); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DrawGridHLine(const PaintInfo: TVTPaintInfo; Left, Right, Top: TDimension); +// Draws a horizontal grid line +var + R: TRect; +begin + R := Rect(Min(Left, Right), Top, Max(Left, Right) + LineWidth, Top + LineWidth); + DrawGridLine(PaintInfo.Canvas, R) +end; + + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DrawGridVLine(const PaintInfo: TVTPaintInfo; Top, Bottom, Left: TDimension; pFixedColumn: Boolean = False); +// Draws a vertical grid line +var + R: TRect; +begin + R := Rect(Left, Min(Top, Bottom), Left + LineWidth, Max(Top, Bottom) + LineWidth); + if pFixedColumn and (TVtPaintOption.toShowVertGridLines in TreeOptions.PaintOptions) then // In case we showe grid lines, we must use a color for the fixed column that differentiates from the normal gridlines + StyleServices.DrawElement(PaintInfo.Canvas.Handle, StyleServices.GetElementDetails(tlGroupHeaderLineOpenHot), R {$IF CompilerVersion >= 34}, @R, CurrentPPI{$IFEND}) + else begin + if StyleServices.IsSystemStyle then // This approach does not work well for many VCL styles, so we added an else case + begin + DrawGridLine(PaintInfo.Canvas, R) + //StyleServices.DrawElement(PaintInfo.Canvas.Handle, StyleServices.GetElementDetails(tlGroupHeaderLineOpenSelectedNotFocused), R {$IF CompilerVersion >= 34}, @R, CurrentPPI{$IFEND}) + end + else begin + DrawGridLine(PaintInfo.Canvas, R) + //StyleServices.DrawElement(PaintInfo.Canvas.Handle, StyleServices.GetElementDetails(tbGroupBoxNormal), R {$IF CompilerVersion >= 34}, @R, CurrentPPI{$IFEND}); + end; + end;// else +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.EndOperation(OperationKind: TVTOperationKind); + +// Called to indicate that a long-running operation has finished. + +begin + Assert(FOperationCount > 0, 'EndOperation must not be called when no operation in progress.'); + System.Dec(FOperationCount); + DoEndOperation(OperationKind); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.EnsureNodeFocused(); +begin + if FocusedNode = nil then + FocusedNode := Self.GetFirstSelected(); + if FocusedNode = nil then + FocusedNode := Self.GetFirstVisible(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.EnsureNodeSelected(pAfterDeletion: Boolean); +begin + if IsEmpty then + exit; // Nothing to do + if (toAlwaysSelectNode in TreeOptions.SelectionOptions) or (pAfterDeletion and (toSelectNextNodeOnRemoval in TreeOptions.SelectionOptions)) then + begin + if (SelectedCount = 0) and not SelectionLocked then + begin + if not Assigned(FNextNodeToSelect) then + begin + FNextNodeToSelect := GetFirstVisible; + // Avoid selecting a disabled node, see #954 + while Assigned(FNextNodeToSelect) and IsDisabled[FNextNodeToSelect] do + FNextNodeToSelect := GetNextVisible(FNextNodeToSelect); + end; + Selected[FNextNodeToSelect] := True; + Self.ScrollIntoView(Self.GetFirstSelected, False); + end;// if nothing selected + EnsureNodeFocused(); + end;//if toAlwaysSelectNode +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.FindNodeInSelection(P: PVirtualNode; var Index: Integer; LowBound, + HighBound: Integer): Boolean; + +// Search routine to find a specific node in the selection array. +// LowBound and HighBound determine the range in which to search the node. +// Either value can be -1 to denote the maximum range otherwise LowBound must be less or equal HighBound. + +var + L, H, + I: Integer; + +begin + Result := False; + L := 0; + if LowBound >= 0 then + L := LowBound; + H := FSelectionCount - 1; + if HighBound >= 0 then + H := HighBound; + while L <= H do + begin + I := (L + H) shr 1; + if PAnsiChar(FSelection[I]) < PAnsiChar(P) then + L := I + 1 + else + begin + H := I - 1; + if FSelection[I] = P then + begin + Result := True; + L := I; + end; + end; + end; + Index := L; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.FinishChunkHeader(Stream: TStream; StartPos, EndPos: Integer); + +// used while streaming out a node to finally write out the size of the chunk + +var + Size: Integer; + +begin + // seek back to the second entry in the chunk header + Stream.Position := StartPos + SizeOf(Size); + // determine size of chunk without the chunk header + Size := EndPos - StartPos - SizeOf(TChunkHeader); + // write the size... + Stream.Write(Size, SizeOf(Size)); + // ... and seek to the last endposition + Stream.Position := EndPos; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.FontChanged(AFont: TObject); + +// Little helper function for font changes (as they are not tracked in TBitmap/TCanvas.OnChange). + +begin + FFontChanged := True; + if Assigned(FOldFontChange) then + FOldFontChange(AFont); + //if not (tsPainting in TreeStates) then AutoScale(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetBorderDimensions: TSize; + +// Returns the overall width of the current window border, depending on border styles. +// Note: these numbers represent the system's standards not special properties, which can be set for TWinControl +// (e.g. bevels, border width). + +var + Styles: Integer; + +begin + Result.cx := 0; + Result.cy := 0; + + Styles := GetWindowLong(Handle, GWL_STYLE); + if (Styles and WS_BORDER) <> 0 then + begin + Dec(Result.cx); + Dec(Result.cy); + end; + if (Styles and WS_THICKFRAME) <> 0 then + begin + Dec(Result.cx, GetSystemMetrics(SM_CXFIXEDFRAME)); + Dec(Result.cy, GetSystemMetrics(SM_CYFIXEDFRAME)); + end; + Styles := GetWindowLong(Handle, GWL_EXSTYLE); + if (Styles and WS_EX_CLIENTEDGE) <> 0 then + begin + Dec(Result.cx, GetSystemMetrics(SM_CXEDGE)); + Dec(Result.cy, GetSystemMetrics(SM_CYEDGE)); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetCheckImage(Node: PVirtualNode; ImgCheckType: TCheckType = ctNone; ImgCheckState: + TCheckState = csUncheckedNormal; ImgEnabled: Boolean = True): Integer; + +// Determines the index into the check image list for the given node depending on the check type +// and enabled state. + +const + // Four dimensional array consisting of image indices for the check type, the check state, the enabled state and the + // hot state. + CheckStateToCheckImage: array[ctCheckBox..ctButton, csUncheckedNormal..csMixedPressed, Boolean, Boolean] of Integer = ( + // ctCheckBox, ctTriStateCheckBox + ( + // csUncheckedNormal (disabled [not hot, hot], enabled [not hot, hot]) + ((ckCheckUncheckedDisabled, ckCheckUncheckedDisabled), (ckCheckUncheckedNormal, ckCheckUncheckedHot)), + // csUncheckedPressed (disabled [not hot, hot], enabled [not hot, hot]) + ((ckCheckUncheckedDisabled, ckCheckUncheckedDisabled), (ckCheckUncheckedPressed, ckCheckUncheckedPressed)), + // csCheckedNormal + ((ckCheckCheckedDisabled, ckCheckCheckedDisabled), (ckCheckCheckedNormal, ckCheckCheckedHot)), + // csCheckedPressed + ((ckCheckCheckedDisabled, ckCheckCheckedDisabled), (ckCheckCheckedPressed, ckCheckCheckedPressed)), + // csMixedNormal + ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedNormal, ckCheckMixedHot)), + // csMixedPressed + ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedPressed, ckCheckMixedPressed)) + ), + // ctRadioButton + ( + // csUncheckedNormal (disabled [not hot, hot], enabled [not hot, hot]) + ((ckRadioUncheckedDisabled, ckRadioUncheckedDisabled), (ckRadioUncheckedNormal, ckRadioUncheckedHot)), + // csUncheckedPressed (disabled [not hot, hot], enabled [not hot, hot]) + ((ckRadioUncheckedDisabled, ckRadioUncheckedDisabled), (ckRadioUncheckedPressed, ckRadioUncheckedPressed)), + // csCheckedNormal + ((ckRadioCheckedDisabled, ckRadioCheckedDisabled), (ckRadioCheckedNormal, ckRadioCheckedHot)), + // csCheckedPressed + ((ckRadioCheckedDisabled, ckRadioCheckedDisabled), (ckRadioCheckedPressed, ckRadioCheckedPressed)), + // csMixedNormal (should never appear with ctRadioButton) + ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedNormal, ckCheckMixedHot)), + // csMixedPressed (should never appear with ctRadioButton) + ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedPressed, ckCheckMixedPressed)) + ), + // ctButton + ( + // csUncheckedNormal (disabled [not hot, hot], enabled [not hot, hot]) + ((ckButtonDisabled, ckButtonDisabled), (ckButtonNormal, ckButtonHot)), + // csUncheckedPressed (disabled [not hot, hot], enabled [not hot, hot]) + ((ckButtonDisabled, ckButtonDisabled), (ckButtonPressed, ckButtonPressed)), + // csCheckedNormal + ((ckButtonDisabled, ckButtonDisabled), (ckButtonNormal, ckButtonHot)), + // csCheckedPressed + ((ckButtonDisabled, ckButtonDisabled), (ckButtonPressed, ckButtonPressed)), + // csMixedNormal (should never appear with ctButton) + ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedNormal, ckCheckMixedHot)), + // csMixedPressed (should never appear with ctButton) + ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedPressed, ckCheckMixedPressed)) + ) + ); + +var + IsHot: Boolean; + +begin + if Assigned(Node) then + begin + ImgCheckType := Node.CheckType; + ImgCheckState := GetCheckState(Node); + ImgEnabled := not (vsDisabled in Node.States) and Self.Enabled; + + IsHot := Node = FCurrentHotNode; + end + else + IsHot := False; + + if ImgCheckState.IsDisabled then begin // disabled image? + // We need to use disabled images, so map ImgCheckState value from disabled to normal, as disabled state is expressed by ImgEnabled. + ImgEnabled := False; + ImgCheckState := ImgCheckState.GetEnabled(); + end;//if + + if ImgCheckType = ctTriStateCheckBox then + ImgCheckType := ctCheckBox; + if IsHot and (ImgCheckState in [csCheckedNormal, csUncheckedNormal]) and (GetKeyState(VK_LBUTTON) < 0) and (hiOnItemCheckbox in FLastHitInfo.HitPositions) then + System.Inc(ImgCheckState); // Advance to pressed state + + if ImgCheckType = ctNone then + Result := -1 + else + Result := CheckStateToCheckImage[ImgCheckType, ImgCheckState, ImgEnabled, IsHot]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetColumnClass: TVirtualTreeColumnClass; + +begin + Result := TVirtualTreeColumn; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetHeaderClass: TVTHeaderClass; + +begin + Result := TVTHeader; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.GetImageIndex(var Info: TVTPaintInfo; Kind: TVTImageKind; InfoIndex: TVTImageInfoIndex); + +// Retrieves the image index and an eventual customized image list for drawing. + +var + CustomImages: TCustomImageList; + +begin + with Info do + begin + ImageInfo[InfoIndex].Index := -1; + ImageInfo[InfoIndex].Ghosted := False; + + CustomImages := DoGetImageIndex(Node, Kind, Column, ImageInfo[InfoIndex].Ghosted, ImageInfo[InfoIndex].Index); + if Assigned(CustomImages) then + ImageInfo[InfoIndex].Images := CustomImages + end; +end; + +function TBaseVirtualTree.GetImageSize(Node: PVirtualNode; Kind: TVTImageKind = TVTImageKind.ikNormal; Column: TColumnIndex = 0; IncludePadding: Boolean = True): TSize; + +// Determines whether the given node has got an image of the given kind in the given column. +// Returns the size of the image, or (0,0) if no image is available +// The given node will be implicitly initialized if needed. + +var + Ghosted: Boolean; + Index: TImageIndex; + lImageList: TCustomImageList; +begin + if not Assigned(OnGetImageIndexEx) and (((Kind = TVTImageKind.ikNormal) and not Assigned(fImages)) + or ((Kind = TVTImageKind.ikState) and not Assigned(fStateImages))) then + begin + Result.cx := 0; + Result.cy := 0; + end; + if not (vsInitialized in Node.States) then + InitNode(Node); + Index := -1; + Ghosted := False; + lImageList := DoGetImageIndex(Node, Kind, Column, Ghosted, Index); + if (Index > NoImage) or (Index = EmptyImage) then begin + if IncludePadding then + Result.cx := lImageList.Width + ScaledPixels(2) + else + Result.cx := lImageList.Width; + Result.cy := lImageList.Height; + end + else begin + Result.cx := 0; + Result.cy := 0; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsEmpty: Boolean; +begin + Result := (Self.ChildCount[nil] = 0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNodeImageSize(Node: PVirtualNode): TSize; + + // Returns the size of an image + // Override if you need different sized images for certain nodes. +begin + Result := GetImageSize(Node); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetMaxRightExtend(): TDimension; + +// Determines the maximum with of the currently visible part of the tree, depending on the length +// of the node texts. This method is used for determining the horizontal scroll range if no columns are used. + +var + Node, + NextNode: PVirtualNode; + TopPosition: TDimension; + CurrentWidth: TDimension; + +begin + Node := GetNodeAt(0, 0, True, TopPosition); + Result := 0; + if not Assigned(Node) then + exit; + + while Assigned(Node) do + begin + if not (vsInitialized in Node.States) then + InitNode(Node); + CurrentWidth := GetOffset(TVTElement.ofsRightOfText, Node); + if Result < (CurrentWidth) then + Result := CurrentWidth; + Inc(TopPosition, NodeHeight[Node]); + if TopPosition > Height then + Break; + + // Get next visible node and update left node position. + NextNode := GetNextVisible(Node, True); + if NextNode = nil then + Break; + Node := NextNode; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.GetNativeClipboardFormats(var Formats: TFormatEtcArray); + +// Returns the supported clipboard formats of the tree. + +begin + TClipboardFormatList.EnumerateFormats(TVirtualTreeClass(ClassType), Formats, FClipboardFormats); + // Ask application/descendants for self defined formats. + DoGetUserClipboardFormats(Formats); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetOperationCanceled; + +begin + Result := FOperationCanceled and (FOperationCount > 0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetOptionsClass: TTreeOptionsClass; + +begin + Result := TCustomVirtualTreeOptions; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.HandleHotTrack(X, Y: TDimension); + +// Updates the current "hot" node. + +var + HitInfo: THitInfo; + CheckPositions: THitPositions; + ButtonIsHit, + DoInvalidate: Boolean; + oldHotNode : PVirtualNode; +begin + if not IsMouseCursorVisible then + begin + if Assigned(FCurrentHotNode) then + begin + InvalidateNode(FCurrentHotNode); + FCurrentHotNode := nil; + end; + Exit; + end;//if not IsMouseCursorVisible + + DoInvalidate := False; + oldHotNode := FCurrentHotNode; + // Get information about the hit. + GetHitTestInfoAt(X, Y, True, HitInfo, []); + + // Only make the new node being "hot" if its label is hit or full row selection is enabled. + CheckPositions := [hiOnItemLabel, hiOnItemCheckbox]; + + // If running under Windows Vista using the explorer theme hitting the buttons makes the node hot, too. + if tsUseExplorerTheme in FStates then + Include(CheckPositions, hiOnItemButtonExact); + + if (CheckPositions * HitInfo.HitPositions = []) and + (not (toFullRowSelect in FOptions.SelectionOptions) or (hiNowhere in HitInfo.HitPositions)) then + FCurrentHotNode := nil + else + FCurrentHotNode := HitInfo.HitNode; + if (FCurrentHotNode <> oldHotNode) or (HitInfo.HitColumn <> FCurrentHotColumn) then + begin + DoInvalidate := (toHotTrack in FOptions.PaintOptions) or (toCheckSupport in FOptions.MiscOptions) or (oldHotNode <> FCurrentHotNode); + DoHotChange(oldHotNode, HitInfo.HitNode); + if Assigned(oldHotNode) and DoInvalidate then + InvalidateNode(oldHotNode); + FCurrentHotColumn := HitInfo.HitColumn; + end; + + ButtonIsHit := (hiOnItemButtonExact in HitInfo.HitPositions); + if Assigned(HitInfo.HitNode) and ((FHotNodeButtonHit <> ButtonIsHit) or (FCurrentHotNode <> oldHotNode) or DoInvalidate) then + begin + FHotNodeButtonHit := ButtonIsHit; + InvalidateNode(HitInfo.HitNode); + end + else + if not Assigned(HitInfo.HitNode) then + FHotNodeButtonHit := False; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.HandleIncrementalSearch(CharCode: Word); + +var + Run, Stop: PVirtualNode; + GetNextNode: TGetNextNodeProc; + NewSearchText: string; + SingleLetter, + PreviousSearch: Boolean; // True if VK_BACK was sent. + SearchDirection: TVTSearchDirection; + + //--------------- local functions ------------------------------------------- + + procedure SetupNavigation; + + // If the search buffer is empty then we start searching with the next node after the last one, otherwise + // we continue with the last one. Node navigation function is set up too here, to avoid frequent checks. + + var + FindNextNode: Boolean; + + begin + FindNextNode := (Length(FSearchBuffer) = 0) or (Run = nil) or SingleLetter or PreviousSearch; + case FIncrementalSearch of + isVisibleOnly: + if SearchDirection = sdForward then + begin + GetNextNode := GetNextVisible; + if FindNextNode then + begin + if Run = nil then + Run := GetFirstVisible(nil, True) + else + begin + Run := GetNextVisible(Run, True); + // Do wrap around. + if Run = nil then + Run := GetFirstVisible(nil, True); + end; + end; + end + else + begin + GetNextNode := GetPreviousVisible; + if FindNextNode then + begin + if Run = nil then + Run := GetLastVisible(nil, True) + else + begin + Run := GetPreviousVisible(Run, True); + // Do wrap around. + if Run = nil then + Run := GetLastVisible(nil, True); + end; + end; + end; + isInitializedOnly: + if SearchDirection = sdForward then + begin + GetNextNode := GetNextNoInit; + if FindNextNode then + begin + if Run = nil then + Run := GetFirstNoInit + else + begin + Run := GetNextNoInit(Run); + // Do wrap around. + if Run = nil then + Run := GetFirstNoInit; + end; + end; + end + else + begin + GetNextNode := GetPreviousNoInit; + if FindNextNode then + begin + if Run = nil then + Run := GetLastNoInit + else + begin + Run := GetPreviousNoInit(Run); + // Do wrap around. + if Run = nil then + Run := GetLastNoInit; + end; + end; + end; + else + // isAll + if SearchDirection = sdForward then + begin + GetNextNode := GetNext; + if FindNextNode then + begin + if Run = nil then + Run := GetFirst + else + begin + Run := GetNext(Run); + // Do wrap around. + if Run = nil then + Run := GetFirst; + end; + end; + end + else + begin + GetNextNode := GetPrevious; + if FindNextNode then + begin + if Run = nil then + Run := GetLast + else + begin + Run := GetPrevious(Run); + // Do wrap around. + if Run = nil then + Run := GetLast; + end; + end; + end; + end; + end; + + //--------------------------------------------------------------------------- + + function CodePageFromLocale(Language: LCID): Integer; + + // Determines the code page for a given locale. + // Unfortunately there is no easier way than this, currently. + + var + Buf: array[0..6] of Char; + + begin + GetLocaleInfo(Language, LOCALE_IDEFAULTANSICODEPAGE, Buf, 6); + Result := StrToIntDef(Buf, GetACP); + end; + + //--------------------------------------------------------------------------- + + function KeyUnicode(C: Char): WideChar; + // Converts the given character into its corresponding Unicode character + // depending on the active keyboard layout. + begin + Result := C; //!!!!!! + end; + + //--------------- end local functions --------------------------------------- + +var + FoundMatch: Boolean; + NewChar: WideChar; + +begin + StopTimer(SearchTimer); + + if FIncrementalSearch <> isNone then + begin + if CharCode <> 0 then + begin + DoStateChange([tsIncrementalSearching]); + + // Convert the given virtual key code into a Unicode character based on the current locale. + NewChar := KeyUnicode(Char(CharCode)); + PreviousSearch := NewChar = WideChar(VK_BACK); + // We cannot do a search with an empty search buffer. + if not PreviousSearch or (FSearchBuffer <> '') then + begin + // Determine which method to use to advance nodes and the start node to search from. + case FSearchStart of + ssAlwaysStartOver: + Run := nil; + ssFocusedNode: + Run := FFocusedNode; + else // ssLastHit + Run := FLastSearchNode; + end; + + // Make sure the start node corresponds to the search criterion. + if Assigned(Run) then + begin + case FIncrementalSearch of + isInitializedOnly: + if not (vsInitialized in Run.States) then + Run := nil; + isVisibleOnly: + if not FullyVisible[Run] or IsEffectivelyFiltered[Run] then + Run := nil; + end; + end; + Stop := Run; + + // VK_BACK temporarily changes search direction to opposite mode. + if PreviousSearch then + begin + if SearchDirection = sdBackward then + SearchDirection := sdForward + else + SearchDirection := sdBackward; + end + else + SearchDirection := FSearchDirection; + // The "single letter mode" is used to advance quickly from node to node when pressing the same key several times. + SingleLetter := (Length(FSearchBuffer) = 1) and not PreviousSearch and (FSearchBuffer[1] = NewChar); + // However if the current hit (if there is one) would fit also with a repeated character then + // don't use single letter mode. + if SingleLetter and (DoIncrementalSearch(Run, FSearchBuffer + NewChar) = 0) then + SingleLetter := False; + SetupNavigation; + FoundMatch := False; + + if Assigned(Run) then + begin + if SingleLetter then + NewSearchText := FSearchBuffer + else + if PreviousSearch then + begin + SetLength(FSearchBuffer, Length(FSearchBuffer) - 1); + NewSearchText := FSearchBuffer; + end + else + NewSearchText := FSearchBuffer + NewChar; + + repeat + if DoIncrementalSearch(Run, NewSearchText) = 0 then + begin + FoundMatch := True; + Break; + end; + + // Advance to next node if we have not found a match. + Run := GetNextNode(Run); + // Do wrap around start or end of tree. + if (Run <> Stop) and (Run = nil) then + SetupNavigation; + until Run = Stop; + end; + + if FoundMatch then + begin + ClearSelection; + FSearchBuffer := NewSearchText; + FLastSearchNode := Run; + FocusedNode := Run; + AddToSelection(Run, False); + FLastSearchNode := Run; + end + else + // Play an acoustic signal if nothing could be found but don't beep if only the currently + // focused node matches. + if Assigned(Run) and (DoIncrementalSearch(Run, NewSearchText) <> 0) then + Beep; + end; + end; + + // Restart search timeout interval. + SetTimer(Handle, SearchTimer, FSearchTimeout, nil); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.HandleMouseDblClick(var Message: TWMMouse; const HitInfo: THitInfo); + +var + Node: PVirtualNode; + MayEdit: Boolean; + +begin + MayEdit := not (tsEditing in FStates) and (toEditOnDblClick in FOptions.MiscOptions); + if tsEditPending in FStates then + begin + StopTimer(EditTimer); + DoStateChange([], [tsEditPending]); + end; + + if not (tsEditing in FStates) or DoEndEdit then + begin + if HitInfo.HitColumn = FHeader.Columns.ClickIndex then + DoColumnDblClick(HitInfo.HitColumn, KeysToShiftState(Message.Keys)); + + if HitInfo.HitNode <> nil then + DoNodeDblClick(HitInfo); + + Node := nil; + if (hiOnItem in HitInfo.HitPositions) and (HitInfo.HitColumn > NoColumn) and + (coFixed in FHeader.Columns[HitInfo.HitColumn].Options) then + begin + if hiUpperSplitter in HitInfo.HitPositions then + Node := GetPreviousVisible(HitInfo.HitNode, True) + else + if hiLowerSplitter in HitInfo.HitPositions then + Node := HitInfo.HitNode; + end; + + if Assigned(Node) and (Node <> FRoot) and (toNodeHeightDblClickResize in FOptions.MiscOptions) then + begin + if DoNodeHeightDblClickResize(Node, HitInfo.HitColumn, KeysToShiftState(Message.Keys), Point(Message.XPos, Message.YPos)) then + begin + SetNodeHeight(Node, FDefaultNodeHeight); + UpdateWindow(); + MayEdit := False; + end; + end + else + if hiOnItemCheckBox in HitInfo.HitPositions then + begin + HandleCheckboxClick(HitInfo.HitNode, Message.Keys); + MayEdit := False; + end// if hiOnItemCheckBox + else + begin + if hiOnItemButton in HitInfo.HitPositions then + begin + ToggleNode(HitInfo.HitNode); + MayEdit := False; + end + else + begin + if toToggleOnDblClick in FOptions.MiscOptions then + begin + if ((([hiOnItemButton, hiOnItemLabel, hiOnNormalIcon, hiOnStateIcon] * HitInfo.HitPositions) <> []) or + ((toFullRowSelect in FOptions.SelectionOptions) and Assigned(HitInfo.HitNode))) then + begin + ToggleNode(HitInfo.HitNode); + MayEdit := False; + end; + end; + end; + end; + end; + + if MayEdit and Assigned(FFocusedNode) and (FFocusedNode = HitInfo.HitNode) and + (FFocusedColumn = HitInfo.HitColumn) and CanEdit(FFocusedNode, HitInfo.HitColumn) then + begin + DoStateChange([tsEditPending]); + FEditColumn := FFocusedColumn; + SetTimer(Handle, EditTimer, 0, nil); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.HandleCheckboxClick(pHitNode: PVirtualNode; pKeys: LongInt); +var + NewCheckState: TCheckState; +begin + NewCheckState := DetermineNextCheckState(pHitNode.CheckType, pHitNode.CheckState); + if (ssLeft in KeysToShiftState(pKeys)) and DoChecking(pHitNode, NewCheckState) then + begin + if (Self.SelectedCount > 1) and (Selected[pHitNode]) and not (toSyncCheckboxesWithSelection in TreeOptions.SelectionOptions) then + SetCheckStateForAll(NewCheckState, True) + else + DoCheckClick(pHitNode, NewCheckState); + end;//if ssLeft +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.HandleMouseDown(var Message: TWMMouse; var HitInfo: THitInfo); + +// centralized mouse button down handling + +var + LastFocused: PVirtualNode; + Column: TColumnIndex; + ShiftState: TShiftState; + + // helper variables to shorten boolean equations/expressions + AutoDrag, // automatic (or allowed) drag start + IsLabelHit, // the node's caption or images are hit + IsCellHit, // for grid extension or full row select (but not check box, button) + IsAnyHit, // either IsHit or IsCellHit + IsHeightTracking, // height tracking + MultiSelect, // multiselection is enabled + ShiftEmpty, // ShiftState = [] + NodeSelected: Boolean; // the new node (if any) is selected + NewColumn: Boolean; // column changed + NewNode: Boolean; // Node changed. + NeedChangeEvent: Boolean; // change event is required for selection change + CanClear: Boolean; + AltPressed: Boolean; // Pressing the Alt key enables special processing for selection. + FullRowDrag: Boolean; // Start dragging anywhere within a node's bound. + NodeRect: TRect; + + //--------------- local functions ------------------------------------------- + + //Fix for issue: 310 whenever there is a need to invalidate a column, consider + //auto spanned columns if applicable + procedure invalidateWithAutoSpan(acolumn: TColumnIndex; anode: PVirtualNode); + var + NextColumn: Integer; + Dummy: TColumnIndex; + begin + if (not FHeader.UseColumns) or (not (toAutoSpanColumns in FOptions.AutoOptions)) + or (acolumn = FHeader.MainColumn) then + begin + //no need to find auto spanned next columns + InvalidateColumn(acolumn); + exit; + end; + //invalidate auto spanned columns too + with FHeader.Columns do //standard loop for auto span + begin + NextColumn := acolumn; + repeat + InvalidateColumn(NextColumn); + Dummy := GetNextVisibleColumn(NextColumn); + if (Dummy = InvalidColumn) or + not ColumnIsEmpty(anode, Dummy) + or + (Items[Dummy].BidiMode <> bdLeftToRight) then + Break; + NextColumn := Dummy; + until False; + end; + end; + + //--------------- end local functions --------------------------------------- + +begin + if tsPanning in FStates then + begin + StopWheelPanning; + Exit; + end; + + if tsEditPending in FStates then + begin + StopTimer(EditTimer); + DoStateChange([], [tsEditPending]); + end; + + FLastHitInfo := HitInfo; // Save for later use in OnNodeClick event, see issue #692 + if (tsEditing in FStates) then begin + if not DoEndEdit then + exit; + // Repeat the hit test as an OnEdited event might got triggered that could modify the tree. + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + end;//if tsEditing + + // Focus change. Don't use the SetFocus method as this does not work for MDI Winapi.Windows. + if not Focused and CanFocus then + begin + Winapi.Windows.SetFocus(Handle); + // Repeat the hit test as an OnExit event might got triggered that could modify the tree. + GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo, KeysToShiftState(Message.Keys)); + FLastHitInfo := HitInfo; // See issue #1297 + end; + + if IsEmpty then + Exit; // Nothing to do + + // Keep clicked column in case the application needs it. + FHeader.Columns.ClickIndex := HitInfo.HitColumn; + + // Change column only if we have hit the node label. + if (hiOnItemLabel in HitInfo.HitPositions) or + (toFullRowSelect in FOptions.SelectionOptions) or + (toGridExtensions in FOptions.MiscOptions) then + begin + NewColumn := FFocusedColumn <> HitInfo.HitColumn; + if toExtendedFocus in FOptions.SelectionOptions then + Column := HitInfo.HitColumn + else + Column := FHeader.MainColumn; + end + else + begin + NewColumn := False; + Column := FFocusedColumn; + end; + + if NewColumn and not FHeader.AllowFocus(Column) then + begin + NewColumn := False; + Column := FFocusedColumn; + end; + + NewNode := FFocusedNode <> HitInfo.HitNode; + + // Translate keys and filter out shift and control key. + ShiftState := KeysToShiftState(Message.Keys) * [ssShift, ssCtrl, ssAlt]; + if ssAlt in ShiftState then + begin + AltPressed := True; + // Remove the Alt key from the shift state. It is not meaningful there. + Exclude(ShiftState, ssAlt); + end + else + AltPressed := False; + + // Various combinations determine what states the tree enters now. + // We initialize shorthand variables to avoid the following expressions getting too large + // and to avoid repeative expensive checks. + IsLabelHit := not AltPressed and not (toSimpleDrawSelection in FOptions.SelectionOptions) and + ((hiOnItemLabel in HitInfo.HitPositions) or (hiOnNormalIcon in HitInfo.HitPositions)); + + IsCellHit := not IsLabelHit and Assigned(HitInfo.HitNode) and + ([hiOnItemButton, hiOnItemCheckBox, hiNoWhere] * HitInfo.HitPositions = []) and + ((toFullRowSelect in FOptions.SelectionOptions) or + ((toGridExtensions in FOptions.MiscOptions) and (HitInfo.HitColumn > NoColumn))); + + IsAnyHit := IsLabelHit or IsCellHit; + MultiSelect := toMultiSelect in FOptions.SelectionOptions; + ShiftEmpty := ShiftState = []; + NodeSelected := IsAnyHit and (vsSelected in HitInfo.HitNode.States); + + // Determine the Drag behavior. + if MultiSelect and not (toDisableDrawSelection in FOptions.SelectionOptions) then + begin + // We have MultiSelect and want to draw a selection rectangle. + // We will start a full row drag only in case a label was hit, + // otherwise a multi selection will start. + FullRowDrag := (toFullRowDrag in FOptions.MiscOptions) and IsCellHit and + not (hiNowhere in HitInfo.HitPositions) and + (NodeSelected or (hiOnItemLabel in HitInfo.HitPositions) or (hiOnNormalIcon in HitInfo.HitPositions)); + end + else // No MultiSelect, hence we can start a drag anywhere in the row. + FullRowDrag := toFullRowDrag in FOptions.MiscOptions; + + IsHeightTracking := (Message.Msg = WM_LBUTTONDOWN) and + (hiOnItem in HitInfo.HitPositions) and + ([hiUpperSplitter, hiLowerSplitter] * HitInfo.HitPositions <> []); + + // Dragging might be started in the inherited handler manually (which is discouraged for stability reasons) + // the test for manual mode is done below (after the focused node is set). + AutoDrag := ((DragMode = TDragMode.dmAutomatic) or Dragging) and (not IsCellHit or FullRowDrag); + + // Query the application to learn if dragging may start now (if set to dmManual). + if Assigned(HitInfo.HitNode) and not AutoDrag and (DragMode = TDragMode.dmManual) then + AutoDrag := DoBeforeDrag(HitInfo.HitNode, Column) and (FullRowDrag or IsLabelHit); + + // handle node height tracking + if IsHeightTracking then + begin + if hiUpperSplitter in HitInfo.HitPositions then + FHeightTrackNode := GetPreviousVisible(HitInfo.HitNode, True) + else + FHeightTrackNode := HitInfo.HitNode; + + if CanSplitterResizeNode(Point(Message.XPos, Message.YPos), FHeightTrackNode, HitInfo.HitColumn) then + begin + FHeightTrackColumn := HitInfo.HitColumn; + NodeRect := GetDisplayRect(FHeightTrackNode, FHeightTrackColumn, False); + FHeightTrackPoint := Point(NodeRect.Left, NodeRect.Top); + DoStateChange([tsNodeHeightTrackPending]); + Exit; + end; + end; + + // handle button clicks + if (hiOnItemButton in HitInfo.HitPositions) and (vsHasChildren in HitInfo.HitNode.States) then + begin + ToggleNode(HitInfo.HitNode); + Exit; + end; + + // check event + if hiOnItemCheckBox in HitInfo.HitPositions then + begin + HandleCheckboxClick(HitInfo.HitNode, Message.Keys); + Exit; + end; + + // Keep this node's level in case we need it for constraint selection. + if (FRoot.ChildCount > 0) and ShiftEmpty or (FSelectionCount = 0) then + if Assigned(HitInfo.HitNode) then + FLastSelectionLevel := GetNodeLevelForSelectConstraint(HitInfo.HitNode) + else + FLastSelectionLevel := GetNodeLevelForSelectConstraint(GetLastVisibleNoInit(nil, True)); + + // immediate clearance + // Determine for the right mouse button if there is a popup menu. In this case and if drag'n drop is pending + // the current selection has to stay as it is. + with HitInfo, Message do + CanClear := not AutoDrag and + (not (tsRightButtonDown in FStates) or not HasPopupMenu(HitNode, HitColumn, Point(XPos, YPos))); + + // pending clearance + if MultiSelect and ShiftEmpty and not (hiOnItemCheckbox in HitInfo.HitPositions) and IsAnyHit and AutoDrag and + NodeSelected and not FSelectionLocked + then + DoStateChange([tsClearPending]); + + // User starts a selection with a selection rectangle. + if not (toDisableDrawSelection in FOptions.SelectionOptions) and not (IsLabelHit or FullRowDrag) and MultiSelect then + begin + SetCapture(Handle); + DoStateChange([tsDrawSelPending]); + FDrawSelShiftState := ShiftState; + FNewSelRect := Rect(Message.XPos + FEffectiveOffsetX, Message.YPos - FOffsetY, Message.XPos + FEffectiveOffsetX, + Message.YPos - FOffsetY); + FLastSelRect := Rect(0, 0, 0, 0); + end; + + NeedChangeEvent := FSelectionCount >= 1; + if not FSelectionLocked and ((not (IsAnyHit or FullRowDrag) and MultiSelect and ShiftEmpty) or + (IsAnyHit and (not NodeSelected or (NodeSelected and CanClear)) and (ShiftEmpty or not MultiSelect or (tsRightButtonDown in FStates)))) then + begin + // If the currently hit node was already selected then we have to reselect it again after clearing the current + // selection, but without a change event if it is the only selected node. + // The same applies if the Alt key is pressed, which allows to start drawing the selection rectangle also + // on node captions and images. Here the previous selection state does not matter, though. + if NodeSelected or (AltPressed and Assigned(HitInfo.HitNode) and (HitInfo.HitColumn = FHeader.MainColumn)) and not (hiNowhere in HitInfo.HitPositions) then + begin + InternalClearSelection; + InternalAddToSelection(HitInfo.HitNode, True); + if NeedChangeEvent then + begin + Invalidate; + Change(HitInfo.HitNode); + end; + end + else if (toAlwaysSelectNode in Self.TreeOptions.SelectionOptions) then + begin + if not (hiNowhere in HitInfo.HitPositions) then + ClearSelection(False) + else + if not (ssCtrl in ShiftState) then + DoStateChange([tsClearOnNewSelection], []); + end + else + ClearSelection(False); + end; + + // pending node edit + if Focused and + ((hiOnItemLabel in HitInfo.HitPositions) or ((toGridExtensions in FOptions.MiscOptions) and + (hiOnItem in HitInfo.HitPositions))) and NodeSelected and not NewColumn and ShiftEmpty and (SelectedCount = 1) then + begin + DoStateChange([tsEditPending]); + end; + + if not (toDisableDrawSelection in FOptions.SelectionOptions) + and not (IsLabelHit or FullRowDrag) and (MultiSelect or (hiNowhere in HitInfo.HitPositions)) then + begin + // The original code here was moved up to fix issue #187. + // In order not to break the semantics of this procedure, we are leaving these if statements here + if not IsCellHit then begin + if NeedChangeEvent then + Change(nil); + Exit; + end; + end; + + // Keep current mouse position. + FLastClickPos := Point(Message.XPos, Message.YPos); + + // Handle selection and node focus change. + if (IsLabelHit or IsCellHit) and + DoFocusChanging(FFocusedNode, HitInfo.HitNode, FFocusedColumn, Column) then + begin + if NewColumn then + begin + + if not Assigned(FFocusedNode) then + InvalidateColumn(FFocusedColumn) + else + invalidateWithAutoSpan(FFocusedColumn, FFocusedNode); //fix: issue 310 + if not Assigned(HitInfo.HitNode) then + InvalidateColumn(Column) + else + invalidateWithAutoSpan(Column, HitInfo.HitNode); //fix: issue 310 + FFocusedColumn := Column; + end; + if DragKind = dkDock then + begin + StopTimer(ScrollTimer); + DoStateChange([], [tsScrollPending, tsScrolling]); + end; + // Get the currently focused node to make multiple multi-selection blocks possible. + LastFocused := FFocusedNode; + if NewNode then + DoFocusNode(HitInfo.HitNode, False); + + if MultiSelect and not ShiftEmpty and not (tsRightButtonDown in FStates) then + HandleClickSelection(LastFocused, HitInfo.HitNode, ShiftState, AutoDrag) + else + begin + if ShiftEmpty then + FRangeAnchor := HitInfo.HitNode; + + // If the hit node is not yet selected then do it now. + if not NodeSelected then + AddToSelection(HitInfo.HitNode, True); + end; + + if NewNode or NewColumn then + begin + ScrollIntoView(FFocusedNode, False, + not (toDisableAutoscrollOnFocus in FOptions.AutoOptions) + and not (toFullRowSelect in FOptions.SelectionOptions)); + + DoFocusChange(FFocusedNode, FFocusedColumn); + end; + end; + + if (SelectedCount = 0) and NeedChangeEvent then + Change(nil); + + // Drag'n drop initiation + // If we lost focus in the interim the button states would be cleared in WM_KILLFOCUS. + if AutoDrag and IsAnyHit and (FStates * [tsLeftButtonDown, tsRightButtonDown, tsMiddleButtonDown] <> []) then + BeginDrag(False); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.HandleMouseUp(var Message: TWMMouse; const HitInfo: THitInfo); + +// Counterpart to the mouse down handler. + +var + ReselectFocusedNode: Boolean; + +begin + ReleaseCapture; + + if not (tsVCLDragPending in FStates) then + begin + // reset pending or persistent states + if IsMouseSelecting then + begin + DoStateChange([], [tsDrawSelecting, tsDrawSelPending, tsToggleFocusedSelection, tsClearOnNewSelection]); + Invalidate; + end; + + if tsClearPending in FStates then + begin + ReselectFocusedNode := Assigned(FFocusedNode) and (vsSelected in FFocusedNode.States); + ClearSelection; + if ReselectFocusedNode then + AddToSelection(FFocusedNode, False); + end; + + if (tsToggleFocusedSelection in FStates) and (HitInfo.HitNode = FFocusedNode) and Assigned(HitInfo.HitNode) then //Prevent AV when dereferencing HitInfo.HitNode below, see bug #100 + begin + if vsSelected in HitInfo.HitNode.States then + begin + if not (toAlwaysSelectNode in TreeOptions.SelectionOptions) or (Self.SelectedCount > 1) then + RemoveFromSelection(HitInfo.HitNode); + end + else + AddToSelection(HitInfo.HitNode, False); + end; + + DoStateChange([], [tsOLEDragPending, tsOLEDragging, tsClearPending, tsDrawSelPending, tsToggleFocusedSelection, + tsScrollPending, tsScrolling]); + StopTimer(ScrollTimer); + + if (FHeader.Columns.ClickIndex > NoColumn) and (FHeader.Columns.ClickIndex = HitInfo.HitColumn) then + DoColumnClick(HitInfo.HitColumn, KeysToShiftState(Message.Keys)); + + if FLastHitInfo.HitNode <> nil then begin // Use THitInfo of mouse down here, see issue #692 + DoNodeClick(FLastHitInfo); + if Assigned(FLastHitInfo.HitNode) then begin + InvalidateNode(FLastHitInfo.HitNode); + FLastHitInfo.HitNode := nil; // prevent firing the event again + end;//if + end; + + // handle a pending edit event + if tsEditPending in FStates then + begin + // Is the mouse still over the same node? + if (HitInfo.HitNode = FFocusedNode) and (hiOnItem in HitInfo.HitPositions) and + (toEditOnClick in FOptions.MiscOptions) and (FFocusedColumn = HitInfo.HitColumn) and + CanEdit(FFocusedNode, HitInfo.HitColumn) then + begin + FEditColumn := FFocusedColumn; + SetTimer(Handle, EditTimer, FEditDelay, nil); + end + else + DoStateChange([], [tsEditPending]); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.HasImage(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex): Boolean; + +// Determines whether the given node has got an image of the given kind in the given column. +// Returns True if so, otherwise False. +// The given node will be implicitly initialized if needed. + +var + Ghosted: Boolean; + Index: TImageIndex; + +begin + if not (vsInitialized in Node.States) then + InitNode(Node); + + Index := -1; + Ghosted := False; + DoGetImageIndex(Node, Kind, Column, Ghosted, Index); + Result := Index > -1; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.HasPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Pos: TPoint): Boolean; + +// Determines whether the tree got a popup menu, either in its PopupMenu property, via the OnGetPopupMenu event or +// through inheritance. The latter case must be checked by the descendant which must override this method. + +begin + Result := Assigned(PopupMenu) or Assigned(DoGetPopupMenu(Node, Column, Pos)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.IncVisibleCount; +begin + System.Inc(FVisibleCount); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InitChildren(Node: PVirtualNode); + +// Initiates the initialization of the child number of the given node. + +var + Count: Cardinal; + +begin + if Assigned(Node) and (Node <> FRoot) and (vsHasChildren in Node.States) then + begin + Count := Node.ChildCount; + if DoInitChildren(Node, Count) then + begin + SetChildCount(Node, Count); + if Count = 0 then + Exclude(Node.States, vsHasChildren); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InitNode(Node: PVirtualNode); + +// Initiates the initialization of the given node to allow the application to load needed data for it. + +var + InitStates: TVirtualNodeInitStates; + MustAdjustInternalVariables: Boolean; + ParentCheckState, SelfCheckState: TCheckState; +begin + with Node^ do + begin + Include(States, vsInitializing); + try + InitStates := []; + if vsInitialized in States then + Include(InitStates, ivsReInit); + Include(States, vsInitialized); + if Parent = FRoot then + DoInitNode(nil, Node, InitStates) + else + DoInitNode(Parent, Node, InitStates); + + // Fix: Any parent check state must be propagated here. + // Because the CheckType is normally set in DoInitNode + // by the App. + if (Node.CheckType = ctTriStateCheckBox) and (toAutoTristateTracking in FOptions.AutoOptions) then + begin + ParentCheckState := Self.GetCheckState(Node.Parent); + SelfCheckState := Self.GetCheckState(Node); + if ((ParentCheckState = csCheckedNormal) + or (ParentCheckState = csUncheckedNormal)) + and (not SelfCheckState.IsDisabled()) + and (SelfCheckState <> ParentCheckState) + and (Parent <> FRoot) + then + SetCheckState(Node, Node.Parent.CheckState); + end + else if (toSyncCheckboxesWithSelection in TreeOptions.SelectionOptions) then + Node.CheckType := TCheckType.ctCheckBox; + + if ivsDisabled in InitStates then + Include(States, vsDisabled); + if ivsHasChildren in InitStates then + Include(States, vsHasChildren); + if ivsSelected in InitStates then + InternalAddToSelection(Node, False); + if ivsMultiline in InitStates then + Include(States, vsMultiline); + if ivsFiltered in InitStates then + begin + MustAdjustInternalVariables := not ((ivsReInit in InitStates) and (vsFiltered in States)); + + Include(States, vsFiltered); + + if not (toShowFilteredNodes in FOptions.PaintOptions) and MustAdjustInternalVariables then + begin + AdjustTotalHeight(Node, -NodeHeight, True); + if FullyVisible[Node] then + System.Dec(FVisibleCount); + if FUpdateCount = 0 then + UpdateScrollBars(True); + end; + end; + + // Expanded may already be set (when called from ReinitNode) or be set in DoInitNode, allow both. + if (vsExpanded in Node.States) xor (ivsExpanded in InitStates) then + begin + // Expand node if not yet done (this will automatically initialize child nodes). + if ivsExpanded in InitStates then + ToggleNode(Node) + else + // If the node already was expanded then explicitly trigger child initialization. + if vsHasChildren in Node.States then + InitChildren(Node); + end; + finally + Exclude(States, vsInitializing); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InternalAddFromStream(Stream: TStream; Version: Integer; Node: PVirtualNode); + +// Loads all details for Node (including its children) from the given stream. +// Because the new nodes might be selected this method also fixes the selection array. + +var + Stop: PVirtualNode; + Index: Integer; + LastTotalHeight: TDimension; + WasFullyVisible: Boolean; + +begin + Assert(Node <> FRoot, 'The root node cannot be loaded from stream.'); + + // Keep the current total height value of Node as it has already been applied + // but might change in the load and fixup code. We have to adjust that afterwards. + LastTotalHeight := Node.TotalHeight; + WasFullyVisible := FullyVisible[Node] and not IsEffectivelyFiltered[Node]; + + // Read in the new nodes. + ReadNode(Stream, Version, Node); + + // One time update of node-internal states and the global visibility counter. + // This is located here to ease and speed up the loading process. + FixupTotalCount(Node); + AdjustTotalCount(Node.Parent, Node.TotalCount - 1, True); // -1 because Node itself was already set. + FixupTotalHeight(Node); + AdjustTotalHeight(Node.Parent, Node.TotalHeight - LastTotalHeight, True); + + // New nodes are always visible, so the visible node count has been increased already. + // If Node is now invisible we have to take back this increment and don't need to add any visible child node. + if not FullyVisible[Node] or IsEffectivelyFiltered[Node] then + begin + if WasFullyVisible then + System.Dec(FVisibleCount); + end + else + // It can never happen that the node is now fully visible but was not before as this would require + // that the visibility state of one of its parents has changed, which cannot happen during loading. + System.Inc(FVisibleCount, CountVisibleChildren(Node)); + + // Fix selection array. + ClearTempCache; + if Node = FRoot then + Stop := nil + else + Stop := Node.NextSibling; + + if toMultiSelect in FOptions.SelectionOptions then + begin + // Add all nodes which were selected before to the current selection (unless they are already there). + while Node <> Stop do + begin + if (vsSelected in Node.States) and not FindNodeInSelection(Node, Index, 0, High(FSelection)) then + InternalCacheNode(Node); + Node := GetNextNoInit(Node); + end; + if FTempNodeCount > 0 then + AddToSelection(FTempNodeCache, FTempNodeCount, True); + ClearTempCache; + end + else // No further selected nodes allowed so delete the corresponding flag in all new nodes. + while Node <> Stop do + begin + Exclude(Node.States, vsSelected); + Node := GetNextNoInit(Node); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.InternalAddToSelection(Node: PVirtualNode; ForceInsert: Boolean): Boolean; +var + lSingletonNodeArray: TNodeArray; +begin + Assert(Assigned(Node), 'Node must not be nil!'); + SetLength(lSingletonNodeArray, 1); + lSingletonNodeArray[0] := Node; + Result := InternalAddToSelection(lSingletonNodeArray, 1, ForceInsert); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.InternalAddToSelection(const NewItems: TNodeArray; NewLength: Integer; + ForceInsert: Boolean): Boolean; + +// Internal version of method AddToSelection which does not trigger OnChange events + +var + I, J: Integer; + CurrentEnd: Integer; + Constrained, + SiblingConstrained: Boolean; + lPreviousSelectedCount: Integer; + AddedNodesSize: Integer; + PTmpNode: PVirtualNode; + +begin + lPreviousSelectedCount := FSelectionCount; + // The idea behind this code is to use a kind of reverse merge sort. QuickSort is quite fast + // and would do the job here too but has a serious problem with already sorted lists like FSelection. + + // current number of valid entries + AddedNodesSize := 0; + + // 1) Remove already selected items, mark all other as being selected. + if ForceInsert then + begin + //Fix: For already selected node when selected, this path + //is used that didn't contain the Constraint logic. Added. + Constrained := toLevelSelectConstraint in FOptions.SelectionOptions; + if Constrained and (FLastSelectionLevel = -1) then + FLastSelectionLevel := GetNodeLevelForSelectConstraint(NewItems[0]); + AddedNodesSize := NewLength; + end + else + begin + Constrained := toLevelSelectConstraint in FOptions.SelectionOptions; + if Constrained and (FLastSelectionLevel = -1) then + FLastSelectionLevel := GetNodeLevelForSelectConstraint(NewItems[0]); + SiblingConstrained := toSiblingSelectConstraint in FOptions.SelectionOptions; + if SiblingConstrained and (FRangeAnchor = nil) then + FRangeAnchor := NewItems[0]; + + for I := 0 to NewLength - 1 do + if ([vsSelected, vsDisabled] * NewItems[I].States <> []) or + (Constrained and (Cardinal(FLastSelectionLevel) <> GetNodeLevel(NewItems[I]))) or + (SiblingConstrained and (FRangeAnchor.Parent <> NewItems[I].Parent)) + then + System.Inc(PAnsiChar(NewItems[I])) // mark as invalid by setting the LSB + else + System.Inc(AddedNodesSize); + end; + + I := PackArray(NewItems, NewLength); + if I > -1 then + NewLength := I; + + Result := NewLength > 0; + if Result then + begin + // 2) Sort the new item list so we can easily traverse it. + if NewLength > 1 then + QuickSort(NewItems, 0, NewLength - 1); + // 3) Make room in FSelection for the new items. + if lPreviousSelectedCount + NewLength >= Length(FSelection) then + SetLength(FSelection, lPreviousSelectedCount + NewLength); + + // 4) Merge in new items + J := NewLength - 1; + CurrentEnd := lPreviousSelectedCount - 1; + + while J >= 0 do + begin + // First insert all new entries which are greater than the greatest entry in the old list. + // If the current end marker is < 0 then there's nothing more to move in the selection + // array and only the remaining new items must be inserted. + if CurrentEnd >= 0 then + begin + while (J >= 0) and (PAnsiChar(NewItems[J]) > PAnsiChar(FSelection[CurrentEnd])) do + begin + FSelection[CurrentEnd + J + 1] := NewItems[J]; + System.Dec(J); + end; + // early out if nothing more needs to be copied + if J < 0 then + Break; + end + else + begin + // insert remaining new entries at position 0 + System.Move(NewItems[0], FSelection[0], (J + 1) * SizeOf(Pointer)); + // nothing more to do so exit main loop + Break; + end; + + // find the last entry in the remaining selection list which is smaller then the largest + // entry in the remaining new items list + FindNodeInSelection(NewItems[J], I, 0, CurrentEnd); + System.Dec(I); + // move all entries which are greater than the greatest entry in the new items list up + // so the remaining gap travels down to where new items must be inserted + System.Move(FSelection[I + 1], FSelection[I + J + 2], (CurrentEnd - I) * SizeOf(Pointer)); + CurrentEnd := I; + end; + + // update selection count + System.Inc(FSelectionCount, AddedNodesSize); + + // post process added nodes + // First set vsSelected flag for all newly selected nodes, then fire event + for I := 0 to AddedNodesSize - 1 do + Include(NewItems[I].States, vsSelected); + + for I := 0 to AddedNodesSize - 1 do + begin + PTmpNode := NewItems[I]; + // call on add event callbackevent + DoAddToSelection(PTmpNode); + if SyncCheckstateWithSelection[PTmpNode] then + checkstate[PTmpNode] := csCheckedNormal; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InternalCacheNode(Node: PVirtualNode); + +// Adds the given node to the temporary node cache (used when collecting possibly large amounts of nodes). + +var + Len: Cardinal; + +begin + Len := Length(FTempNodeCache); + if FTempNodeCount = Len then + begin + if Len < 100 then + Len := 100 + else + Len := Len + Len div 10; + SetLength(FTempNodeCache, Len); + end; + FTempNodeCache[FTempNodeCount] := Node; + System.Inc(FTempNodeCount); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InternalClearSelection(); + +var + Count: Integer; + lNode: PVirtualNode; +begin + // It is possible that there are invalid node references in the selection array + // if the tree update is locked and changes in the structure were made. + // Handle this potentially dangerous situation by packing the selection array explicitely. + if IsUpdating then + begin + Count := PackArray(FSelection, FSelectionCount); + if Count > -1 then + begin + FSelectionCount := Count; + SetLength(FSelection, FSelectionCount); + end; + end; + + while FSelectionCount > 0 do + begin + System.Dec(FSelectionCount); + lNode := FSelection[FSelectionCount]; + //sync path note: deselect when click on another or on outside area + Exclude(lNode.States, vsSelected); + if SyncCheckstateWithSelection[lNode] then + CheckState[lNode] := csUncheckedNormal; + DoRemoveFromSelection(lNode); + end; + ResetRangeAnchor; + FSelection := nil; + DoStateChange([], [tsClearPending]); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InternalConnectNode(Node, Destination: PVirtualNode; Target: TBaseVirtualTree; + Mode: TVTNodeAttachMode); + +// Connects Node with Destination depending on Mode. +// No error checking takes place. Node as well as Destination must be valid. Node must never be a root node and +// Destination must not be a root node if Mode is amInsertBefore or amInsertAfter. + +var + Run: PVirtualNode; + +begin + // Keep in mind that the destination node might belong to another tree. + with Target do + begin + case Mode of + amInsertBefore: + begin + Node.SetPrevSibling(Destination.PrevSibling); + Destination.SetPrevSibling(Node); + Node.SetNextSibling(Destination); + Node.SetParent(Destination.Parent); + Node.SetIndex(Destination.Index); + if Node.PrevSibling = nil then + Node.Parent.SetFirstChild(Node) + else + Node.PrevSibling.SetNextSibling(Node); + + // reindex all following nodes + Run := Destination; + while Assigned(Run) do + begin + Run.SetIndex(Run.Index + 1); + Run := Run.NextSibling; + end; + end; + amInsertAfter: + begin + Node.SetNextSibling(Destination.NextSibling); + Destination.SetNextSibling(Node); + Node.SetPrevSibling(Destination); + Node.SetParent(Destination.Parent); + if Node.NextSibling = nil then + Node.Parent.SetLastChild(Node) + else + Node.NextSibling.SetPrevSibling(Node); + Node.SetIndex(Destination.Index); + + // reindex all following nodes + Run := Node; + while Assigned(Run) do + begin + Run.SetIndex(Run.Index + 1); + Run := Run.NextSibling; + end; + end; + amAddChildFirst: + begin + if Assigned(Destination.FirstChild) then + begin + // If there's a first child then there must also be a last child. + Destination.FirstChild.SetPrevSibling(Node); + Node.SetNextSibling(Destination.FirstChild); + Destination.SetFirstChild(Node); + end + else + begin + // First child node at this location. + Destination.SetFirstChild(Node); + Destination.SetLastChild(Node); + Node.SetNextSibling(nil); + end; + Node.SetPrevSibling(nil); + Node.SetParent(Destination); + Node.SetIndex(0); + // reindex all following nodes + Run := Node.NextSibling; + while Assigned(Run) do + begin + Run.SetIndex(Run.Index + 1); + Run := Run.NextSibling; + end; + end; + amAddChildLast: + begin + if Assigned(Destination.LastChild) then + begin + // If there's a last child then there must also be a first child. + Destination.LastChild.SetNextSibling(Node); + Node.SetPrevSibling(Destination.LastChild); + Destination.SetLastChild(Node); + end + else + begin + // first child node at this location + Destination.SetFirstChild(Node); + Destination.SetLastChild(Node); + Node.SetPrevSibling(nil); + end; + Node.SetNextSibling(nil); + Node.SetParent(Destination); + if Assigned(Node.PrevSibling) then + Node.SetIndex(Node.PrevSibling.Index + 1) + else + Node.SetIndex(0); + end; + else + // amNoWhere: do nothing + end; + // Remove temporary states. + Node.States := Node.States - [vsChecking, vsCutOrCopy, vsDeleting]; + + if (Mode <> amNoWhere) then begin + Node.Parent.SetChildCount(Node.Parent.ChildCount + 1); + Include(Node.Parent.States, vsHasChildren); + AdjustTotalCount(Node.Parent, Node.TotalCount, True); + + // Add the new node's height only if its parent is expanded. + if (vsExpanded in Node.Parent.States) and (vsVisible in Node.States) then begin + AdjustTotalHeight(Node.Parent, Node.TotalHeight, True); + System.Inc(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1))); + end;//if + + // Update the hidden children flag of the parent. + if (Node.Parent <> FRoot) then + begin + // If we have added a visible node then simply remove the all-children-hidden flag. + if IsEffectivelyVisible[Node] then + Exclude(Node.Parent.States, vsAllChildrenHidden) + else begin + // If we have added an invisible node and this is the only child node then + // make sure the all-children-hidden flag is in a determined state. + // If there were child nodes before then no action is needed. + if Node.Parent.ChildCount = 1 then + Include(Node.Parent.States, vsAllChildrenHidden); + end;//else + end; //if Node.Parent <> FRoot + end;//if Mode <> amNoWhere + end;//With +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.InternalData(Node: PVirtualNode): Pointer; + +begin + Result := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InternalDisconnectNode(Node: PVirtualNode; KeepFocus: Boolean; Reindex: Boolean = True; ParentClearing: Boolean = False); + +// Disconnects the given node from its parent and siblings. The node's pointer are not reset so they can still be used +// after return from this method (probably a very short time only!). +// If KeepFocus is True then the focused node is not reset. This is useful if the given node is reconnected to the tree +// immediately after return of this method and should stay being the focused node if it was it before. +// Note: Node must not be nil or the root node. + +var + Parent, + Run: PVirtualNode; + Index: Integer; + AdjustHeight: Boolean; + +begin + Assert(Assigned(Node) and (Node <> FRoot), 'Node must neither be nil nor the root node.'); + + if (Node = FFocusedNode) and not KeepFocus then + begin + DoFocusNode(nil, False); + DoFocusChange(FFocusedNode, FFocusedColumn); + end; + + if Node = FRangeAnchor then + ResetRangeAnchor; + + // Update the hidden children flag of the parent. + if (Node.Parent <> FRoot) and not (ParentClearing) then + if FUpdateCount = 0 then + DetermineHiddenChildrenFlag(Node.Parent) + else + Include(FStates, tsUpdateHiddenChildrenNeeded); + + if not (vsDeleting in Node.States) then + begin + // Some states are only temporary so take them out. + Node.States := Node.States - [vsChecking]; + Parent := Node.Parent; + Parent.SetChildCount(Parent.ChildCount - 1); + AdjustHeight := (vsExpanded in Parent.States) and (vsVisible in Node.States); + if Parent.ChildCount = 0 then + begin + Parent.States := Parent.States - [vsAllChildrenHidden, vsHasChildren]; + if (Parent <> FRoot) and (vsExpanded in Parent.States) then + Exclude(Parent.States, vsExpanded); + end; + AdjustTotalCount(Parent, -Integer(Node.TotalCount), True); + if AdjustHeight then + AdjustTotalHeight(Parent, -Node.TotalHeight, True); + if FullyVisible[Node] then + System.Dec(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1))); + + if Assigned(Node.PrevSibling) then + Node.PrevSibling.SetNextSibling(Node.NextSibling) + else + Parent.SetFirstChild(Node.NextSibling); + + if Assigned(Node.NextSibling) then + begin + Node.NextSibling.SetPrevSibling(Node.PrevSibling); + // Reindex all following nodes. + if Reindex then + begin + Run := Node.NextSibling; + Index := Node.Index; + while Assigned(Run) do + begin + Run.SetIndex(Index); + System.Inc(Index); + Run := Run.NextSibling; + end; + end; + end + else + Parent.SetLastChild(Node.PrevSibling); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InternalRemoveFromSelection(Node: PVirtualNode); + +// Special version to mark a node to be no longer in the current selection. PackArray must +// be used to remove finally those entries. + +var + Index: Integer; + +begin + // Because pointers are always DWORD aligned we can simply increment all those + // which we want to have removed (see also PackArray) and still have the + // order in the list preserved. + if FindNodeInSelection(Node, Index, -1, -1) then + begin + //sync path note: deselect when overlapping drawselection is made + Exclude(Node.States, vsSelected); + if SyncCheckstateWithSelection[Node] then + Node.CheckState := csUncheckedNormal; // Avoid using SetCheckState() as it handles toSyncCheckboxesWithSelection as well. + System.Inc(PAnsiChar(FSelection[Index])); + DoRemoveFromSelection(Node); + Change(Node); // Calling Change() here fixes issue #1047 + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InternalSetFocusedColumn(const index: TColumnIndex); +begin + FFocusedColumn := index; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InvalidateCache; + +// Marks the cache as invalid. + +begin + DoStateChange([tsValidationNeeded], [tsUseCache]); + //ChangeTreeStatesAsync([csValidationNeeded], [csUseCache]); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.MarkCutCopyNodes; + +// Sets the vsCutOrCopy style in every currently selected but not disabled node to indicate it is +// now part of a clipboard operation. + +var + Nodes: TNodeArray; + I: Integer; + +begin + Nodes := nil; + if FSelectionCount > 0 then + begin + // need the current selection sorted to exclude selected nodes which are children, grandchildren etc. of + // already selected nodes + Nodes := GetSortedSelection(False); + for I := 0 to High(Nodes) do + with Nodes[I]^ do + if not (vsDisabled in States) then + Include(States, vsCutOrCopy); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.Loaded; + +var + LastRootCount: Cardinal; + IsReadOnly: Boolean; + +begin + inherited; + + // Call RegisterDragDrop after all visual inheritance changes to MiscOptions have been applied. + if not (csDesigning in ComponentState) and HandleAllocated and ((hoDrag in Header.Options) or (toAcceptOLEDrop in TreeOptions.MiscOptions)) then + RegisterDragDrop(Handle, DragManager as IDropTarget); + + // If a root node count has been set during load of the tree then update its child structure now + // as this hasn't been done yet in this case. + if (tsNeedRootCountUpdate in FStates) and (FRoot.ChildCount > 0) then + begin + DoStateChange([], [tsNeedRootCountUpdate]); + IsReadOnly := toReadOnly in FOptions.MiscOptions; + FOptions.InternalSetMiscOptions(FOptions.MiscOptions - [toReadOnly]); + LastRootCount := FRoot.ChildCount; + FRoot.SetChildCount(0); + BeginUpdate; + SetChildCount(FRoot, LastRootCount); + EndUpdate; + if IsReadOnly then + FOptions.InternalSetMiscOptions(FOptions.MiscOptions + [toReadOnly]); + end; + + // Prevent the object inspector at design time from marking the header as being modified + // when auto resize is enabled. + Updating; + try + TVTHeaderCracker(FHeader).UpdateMainColumn; + TVirtualTreeColumnsCracker(FHeader.Columns).FixPositions; + if toAutoBidiColumnOrdering in FOptions.AutoOptions then + TVirtualTreeColumnsCracker(FHeader.Columns).ReorderColumns(UseRightToLeftAlignment); + // Because of the special recursion and update stopper when creating the window (or resizing it) + // we have to manually trigger the auto size calculation here. + if hsNeedScaling in FHeader.States then + TVTHeaderCracker(FHeader).RescaleHeader + else + TVTHeaderCracker(FHeader).RecalculateHeader; + if hoAutoResize in FHeader.Options then + TVirtualTreeColumnsCracker(FHeader.Columns).AdjustAutoSize(InvalidColumn, True); + finally + Updated; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.MainColumnChanged; + +begin + DoCancelEdit; + NotifyAccessibleEvent(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.MouseMove(Shift: TShiftState; X, Y: TDimension); + +var + R: TRect; + +begin + if tsNodeHeightTrackPending in FStates then + begin + // Remove hint if shown currently. + Application.CancelHint; + + // Stop wheel panning if active. + StopWheelPanning; + + // Stop timers + StopTimer(ExpandTimer); + StopTimer(EditTimer); + StopTimer(HeaderTimer); + StopTimer(ScrollTimer); + StopTimer(SearchTimer); + FSearchBuffer := ''; + FLastSearchNode := nil; + + DoStateChange([tsNodeHeightTracking], [tsScrollPending, tsScrolling, tsEditPending, tsOLEDragPending, tsVCLDragPending, + tsIncrementalSearching, tsNodeHeightTrackPending]); + end; + + if tsDrawSelPending in FStates then + begin + // Remove current selection in case the user clicked somewhere in the window (but not a node) + // and moved the mouse. + if CalculateSelectionRect(X, Y) then + begin + InvalidateRect(@FNewSelRect, False); + UpdateWindow(); + if (Abs(FNewSelRect.Right - FNewSelRect.Left) > Mouse.DragThreshold) or + (Abs(FNewSelRect.Bottom - FNewSelRect.Top) > Mouse.DragThreshold) then + begin + if tsClearPending in FStates then + begin + DoStateChange([], [tsClearPending]); + ClearSelection; + end; + DoStateChange([tsDrawSelecting], [tsDrawSelPending]); + + // Reset to main column for multiselection. + FocusedColumn := FHeader.MainColumn; + + // The current rectangle may already include some node captions. Handle this. + if HandleDrawSelection(X, Y) then + InvalidateRect(nil, False); + end; + end; + end + else + begin + if tsNodeHeightTracking in FStates then + begin + // Handle height tracking. + if DoNodeHeightTracking(FHeightTrackNode, FHeightTrackColumn, TVTHeaderCracker(FHeader).GetShiftState, FHeightTrackPoint, Point(X, Y)) then + begin + // Avoid negative (or zero) node heights. + if FHeightTrackPoint.Y >= Y then + Y := FHeightTrackPoint.Y + 1; + SetNodeHeight(FHeightTrackNode, Y - FHeightTrackPoint.Y); + UpdateWindow(); + Exit; + end; + end; + + // Really start dragging if the mouse has been moved more than the threshold. + if (tsOLEDragPending in FStates) and + ( + ((Abs(FLastClickPos.X - X) >= FDragThreshold) and (X > 0)) or // Check >0 to fix issue #833 + ((Abs(FLastClickPos.Y - Y) >= FDragThreshold) and (Y > 0)) + ) + then + DoDragging(FLastClickPos) + else + begin + if CanAutoScroll then + DoAutoScroll(X, Y); + if tsPanning in FStates then + AdjustPanningCursor(X, Y); + if not IsMouseSelecting then + begin + HandleHotTrack(X, Y); + inherited MouseMove(Shift, X, Y); + end + else + begin + // Handle draw selection if required, but don't do the work twice if the + // auto scrolling code already cares about the selection. + if not (tsScrolling in FStates) and CalculateSelectionRect(X, Y) then + begin + // If something in the selection changed then invalidate the entire + // tree instead trying to figure out the display rects of all changed nodes. + if HandleDrawSelection(X, Y) then + InvalidateRect(nil, False) + else + begin + UnionRect(R, OrderRect(FNewSelRect), OrderRect(FLastSelRect)); + OffsetRect(R, -FEffectiveOffsetX, FOffsetY); + InvalidateRect(@R, False); + end; + UpdateWindow(); + end; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.Notification(AComponent: TComponent; Operation: TOperation); + +begin + if (AComponent <> Self) and (Operation = opRemove) then + begin + // Check for components linked to the tree. + if AComponent = FImages then + begin + Images := nil; + if not (csDestroying in ComponentState) then + Invalidate; + end + else + if AComponent = FStateImages then + begin + StateImages := nil; + if not (csDestroying in ComponentState) then + Invalidate; + end + else + if AComponent = FCustomCheckImages then + begin + CustomCheckImages := nil; + FCheckImageKind := ckSystemDefault; + if not (csDestroying in ComponentState) then + Invalidate; + end + else + if AComponent = PopupMenu then + PopupMenu := nil + else + // Check for components linked to the header. + if Assigned(FHeader) then + begin + if AComponent = FHeader.Images then + FHeader.Images := nil + else + if AComponent = FHeader.PopupMenu then + FHeader.PopupMenu := nil; + end; + end; + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.OriginalWMNCPaint(DC: HDC); + +// Unfortunately, the painting for the non-client area in TControl is not always correct and does also not consider +// existing clipping regions, so it has been modified here to take this into account. + +const + InnerStyles: array[TBevelCut] of Integer = (0, BDR_SUNKENINNER, BDR_RAISEDINNER, 0); + OuterStyles: array[TBevelCut] of Integer = (0, BDR_SUNKENOUTER, BDR_RAISEDOUTER, 0); + EdgeStyles: array[TBevelKind] of Integer = (0, 0, BF_SOFT, BF_FLAT); + Ctl3DStyles: array[Boolean] of Integer = (BF_MONO, 0); + +var + RC, RW: TRect; + EdgeSize: Integer; + Size: TSize; + +begin + if (BevelKind <> bkNone) or (BorderWidth > 0) then + begin + RC := Rect(0, 0, Width, Height); + Size := GetBorderDimensions; + InflateRect(RC, Size.cx, Size.cy); + + RW := RC; + + if BevelKind <> bkNone then + begin + DrawEdge(DC, RC, InnerStyles[BevelInner] or OuterStyles[BevelOuter], Byte(BevelEdges) or EdgeStyles[BevelKind] or + Ctl3DStyles[Ctl3D]); + + EdgeSize := 0; + if BevelInner <> bvNone then + Inc(EdgeSize, BevelWidth); + if BevelOuter <> bvNone then + Inc(EdgeSize, BevelWidth); + if beLeft in BevelEdges then + Inc(RC.Left, EdgeSize); + if beTop in BevelEdges then + Inc(RC.Top, EdgeSize); + if beRight in BevelEdges then + Dec(RC.Right, EdgeSize); + if beBottom in BevelEdges then + Dec(RC.Bottom, EdgeSize); + end; + + // Repaint only the part in the original clipping region and not yet drawn parts. + IntersectClipRect(DC, RC.Left, RC.Top, RC.Right, RC.Bottom); + + // Determine inner rectangle to exclude (RC corresponds then to the client area). + InflateRect(RC, -Integer(BorderWidth), -Integer(BorderWidth)); + + // Remove the inner rectangle. + ExcludeClipRect(DC, RC.Left, RC.Top, RC.Right, RC.Bottom); + + // Erase parts not drawn. + Brush.Color := FColors.BorderColor; + Winapi.Windows.FillRect(DC, RW, Brush.Handle); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.Paint; + +// Window paint routine. Used when the tree window needs to be updated. + +var + Window: TRect; + Target: TPoint; + Temp: TDimension; + Options: TVTInternalPaintOptions; + RTLOffset: TDimension; + +begin + + Options := [poBackground, poColumnColor, poDrawFocusRect, poDrawDropMark, poDrawSelection, poGridLines]; + if UseRightToLeftAlignment and FHeader.UseColumns then + RTLOffset := ComputeRTLOffset(True) + else + RTLOffset := 0; + + // The update rect has already been filled in WMPaint, as it is the window's update rect, which gets + // reset when BeginPaint is called (in the ancestor). + // The difference to the DC's clipbox is that it is also valid with internal paint operations used + // e.g. by the Explorer while dragging, but show window content while dragging is disabled. + if not IsRectEmpty(FUpdateRect) then + begin + Temp := Header.Columns.GetVisibleFixedWidth; + if Temp = 0 then + begin + Window := FUpdateRect; + Target := Window.TopLeft; + + // The clipping rectangle is given in client coordinates of the window. We have to convert it into + // a sliding window of the tree image. + OffsetRect(Window, FEffectiveOffsetX - RTLOffset, -FOffsetY); + PaintTree(Canvas, Window, Target, Options); + end + else + begin + // First part, fixed columns + Window := ClientRect; + Window.Right := Temp; + Target := Window.TopLeft; + + OffsetRect(Window, -RTLOffset, -FOffsetY); + PaintTree(Canvas, Window, Target, Options); + + // Second part, other columns + Window := GetClientRect; + + if Temp > Window.Right then + Exit; + + Window.Left := Temp; + Target := Window.TopLeft; + + OffsetRect(Window, FEffectiveOffsetX - RTLOffset, -FOffsetY); + PaintTree(Canvas, Window, Target, Options); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.PaintCheckImage(Canvas: TCanvas; const ImageInfo: TVTImageInfo; Selected: Boolean); + +var + ForegroundColor: COLORREF; + R: TRect; + Details, lSizeDetails: TThemedElementDetails; + lSize: TSize; + Theme: HTHEME; + lCheckImages: TCustomImageList; +begin + with ImageInfo do + begin + if (tsUseThemes in FStates) and (FCheckImageKind = ckSystemDefault) then + begin + Details.Element := teButton; + case Index of + // ctRadioButton + 1 : Details := StyleServices.GetElementDetails(tbRadioButtonUncheckedNormal); + 2 : Details := StyleServices.GetElementDetails(tbRadioButtonUncheckedHot); + 3 : Details := StyleServices.GetElementDetails(tbRadioButtonUncheckedPressed); + 4 : Details := StyleServices.GetElementDetails(tbRadioButtonUncheckedDisabled); + 5 : Details := StyleServices.GetElementDetails(tbRadioButtonCheckedNormal); + 6 : Details := StyleServices.GetElementDetails(tbRadioButtonCheckedHot); + 7 : Details := StyleServices.GetElementDetails(tbRadioButtonCheckedPressed); + 8 : Details := StyleServices.GetElementDetails(tbRadioButtonCheckedDisabled); + // ct(TriState)CheckBox + 9 : Details := StyleServices.GetElementDetails(tbCheckBoxUncheckedNormal); + 10 : Details := StyleServices.GetElementDetails(tbCheckBoxUncheckedHot); + 11 : Details := StyleServices.GetElementDetails(tbCheckBoxUncheckedPressed); + 12 : Details := StyleServices.GetElementDetails(tbCheckBoxUncheckedDisabled); + 13 : Details := StyleServices.GetElementDetails(tbCheckBoxCheckedNormal); + 14 : Details := StyleServices.GetElementDetails(tbCheckBoxCheckedHot); + 15 : Details := StyleServices.GetElementDetails(tbCheckBoxCheckedPressed); + 16 : Details := StyleServices.GetElementDetails(tbCheckBoxCheckedDisabled); + 17 : Details := StyleServices.GetElementDetails(tbCheckBoxMixedNormal); + 18 : Details := StyleServices.GetElementDetails(tbCheckBoxMixedHot); + 19 : Details := StyleServices.GetElementDetails(tbCheckBoxMixedPressed); + 20 : Details := StyleServices.GetElementDetails(tbCheckBoxMixedDisabled); + // ctButton + ckButtonNormal: Details := StyleServices.GetElementDetails(tbPushButtonNormal); + ckButtonHot: Details := StyleServices.GetElementDetails(tbPushButtonHot); + ckButtonPressed: Details := StyleServices.GetElementDetails(tbPushButtonPressed); + ckButtonDisabled: Details := StyleServices.GetElementDetails(tbPushButtonDisabled); + else + Details := StyleServices.GetElementDetails(tbButtonRoot); + end; + if StyleServices.IsSystemStyle {and not (Index in [ckButtonNormal..ckButtonDisabled])} then + begin + Theme := OpenThemeData(Handle, 'BUTTON'); + GetThemePartSize(Theme, Canvas.Handle, Details.Part, Details.State, nil, TS_TRUE, lSize); + if (Index in [ckButtonNormal..ckButtonDisabled]) then begin + lSizeDetails := StyleServices.GetElementDetails(tbCheckBoxCheckedNormal); // Size of dropdown button should be based on size of checkboxes + GetThemePartSize(Theme, Canvas.Handle, lSizeDetails.Part, lSizeDetails.State, nil, TS_TRUE, lSize); + // dropdown buttons should be slightly larger than checkboxes, see issue #887 + lSize.cx := Round(lSize.cx * 1.15); + lSize.cy := Round(lSize.cy * 1.1); + end; + R := Rect(XPos, YPos, XPos + lSize.cx, YPos + lSize.cy); + if (Index in [ckButtonNormal..ckButtonDisabled]) then + R.Offset(-1, 0); // Eliminate 1 pixel border around Windows themed button + DrawThemeBackground(Theme, Canvas.Handle, Details.Part, Details.State, R, nil); + CloseThemeData(Theme); + end + else + begin + if (Index in [ckButtonNormal..ckButtonDisabled]) or not StyleServices.GetElementSize(Canvas.Handle, Details, TElementSize.esActual, lSize{$IF CompilerVersion >= 34}, CurrentPPI{$IFEND}) then begin + // radio buttons fail in RAD Studio 10 Seattle and lower, fallback to checkbox images. See issue #615 + if not StyleServices.GetElementSize(Canvas.Handle, StyleServices.GetElementDetails(tbCheckBoxUncheckedNormal), TElementSize.esActual, lSize{$IF CompilerVersion >= 34}, CurrentPPI{$IFEND}) then + lSize := TSize.Create(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK)); + end;//if + R := Rect(XPos, YPos, XPos + lSize.cx, YPos + lSize.cy); + StyleServices.DrawElement(Canvas.Handle, Details, R {$IF CompilerVersion >= 34}, nil, FCurrentPPI{$IFEND}); + Canvas.Refresh; // Every time you give a Canvas.Handle away to some other code you can't control you have to call Canvas.Refresh afterwards because the Canvas object and the HDC can be out of sync. + end; + if (Index in [ckButtonNormal..ckButtonDisabled]) then begin + Canvas.Pen.Color := clGray; + // These constants have been determined by test using various themes and dpi-scalings + DrawArrow(Canvas, TScrollDirection.sdDown, Point(R.Left + Round(lSize.cx * 0.22), R.Top + Round(lSize.cy * 0.33)), Round(lSize.cx *0.28)); + end;//if + end + else begin + if Assigned(FCheckImages) then + lCheckImages := FCheckImages + else + lCheckImages := FCustomCheckImages; + with lCheckImages do + begin + if Selected and not Ghosted then + begin + if Focused or (TVTPaintOption.toPopupMode in FOptions.PaintOptions) then + ForegroundColor := ColorToRGB(FColors.FocusedSelectionColor) + else + ForegroundColor := ColorToRGB(FColors.UnfocusedSelectionColor); + end + else + ForegroundColor := GetRGBColor(BlendColor); + + ImageList_DrawEx(Handle, Index, Canvas.Handle, XPos, YPos, 0, 0, GetRGBColor(BkColor), ForegroundColor, + ILD_TRANSPARENT); + end; + end; //else + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + + +procedure TBaseVirtualTree.PaintImage(var PaintInfo: TVTPaintInfo; ImageInfoIndex: TVTImageInfoIndex; DoOverlay: Boolean); +const + Style: array[TImageType] of Cardinal = (0, ILD_MASK); +var + ExtraStyle: Cardinal; + CutNode: Boolean; + PaintFocused: Boolean; + DrawEnabled: Boolean; + CustomOverlayDrawing: Boolean; // False if the built-in overloay drawing of TImageList should be used, True if custom drawing should take place. +begin + with PaintInfo do + begin + CutNode := (vsCutOrCopy in Node.States) and (tsCutPending in FStates); + PaintFocused := Focused or (toGhostedIfUnfocused in FOptions.PaintOptions); + + // Since the overlay image must be specified together with the image to draw + // it is meaningfull to retrieve it in advance. + if DoOverlay then + GetImageIndex(PaintInfo, ikOverlay, iiOverlay) + else + PaintInfo.ImageInfo[iiOverlay].Index := -1; + + DrawEnabled := not (vsDisabled in Node.States) and Enabled; + with ImageInfo[ImageInfoIndex] do + begin + if (vsSelected in Node.States) and not(Ghosted or CutNode) then + begin + if PaintFocused or (toPopupMode in FOptions.PaintOptions) then + Images.BlendColor := FColors.FocusedSelectionColor + else + Images.BlendColor := FColors.UnfocusedSelectionColor; + end + else + Images.BlendColor := Color; + + ExtraStyle := ILD_TRANSPARENT; + // If the user returned an index >= 15 then we cannot use the built-in overlay image drawing. + // Instead we do it manually. Also of the image list of the normal and the overlay icon is different, + // we can't use the built-in drawing. See issue #779. + if (ImageInfo[iiOverlay].Index > -1) then begin + CustomOverlayDrawing := (ImageInfo[iiOverlay].Index >= 15) or (ImageInfo[iiOverlay].Images <> ImageInfo[iiNormal].Images); + if not CustomOverlayDrawing then + ExtraStyle := ILD_TRANSPARENT or ILD_OVERLAYMASK and IndexToOverlayMask(ImageInfo[iiOverlay].Index + 1); + end + else + CustomOverlayDrawing := False; + + // Blend image if enabled and the tree has the focus (or ghosted images must be drawn also if unfocused) ... + if (toUseBlendedImages in FOptions.PaintOptions) and PaintFocused + // ... and the image is ghosted... + and (Ghosted or + // ... or it is not the check image and the node is selected (but selection is not for the entire row)... + ((vsSelected in Node.States) and + not (toFullRowSelect in FOptions.SelectionOptions) and + not (toGridExtensions in FOptions.MiscOptions)) or + // ... or the node must be shown in cut mode. + CutNode) then + ExtraStyle := ExtraStyle or ILD_BLEND50; + + if (vsSelected in Node.States) and not Ghosted then + Images.BlendColor := clDefault; + + DrawImage(Images, Index, Canvas, XPos, YPos, Style[Images.ImageType] or ExtraStyle, DrawEnabled); + + // Now, draw the overlay. This circumnavigates limitations in the overlay mask index (it has to be 4 bits in size, + // anything larger will be truncated by the ILD_OVERLAYMASK). + // However this will only be done if the overlay image index is > 15, to avoid breaking code that relies + // on overlay image indices (e.g. when using system image lists). + if CustomOverlayDrawing then begin + ExtraStyle := ExtraStyle and not ILD_BLEND50; // Fixes issue #551 + // Note: XPos and YPos are those of the normal images. + DrawImage(ImageInfo[iiOverlay].Images, ImageInfo[iiOverlay].Index, Canvas, XPos, YPos, + Style[ImageInfo[iiOverlay].Images.ImageType] or ExtraStyle, DrawEnabled); + end;//if + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.PaintNodeButton(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const R: TRect; + ButtonX, ButtonY: TDimension; BidiMode: TBiDiMode); + +var + Bitmap: TBitmap; + XPos: TDimension; + IsHot: Boolean; + IsSelected : boolean; + Theme: HTHEME; + Glyph: Integer; + State: Integer; + Pos: TRect; + +begin + IsHot := (FCurrentHotNode = Node) and FHotNodeButtonHit; + IsSelected := (vsSelected in Node.States); + + // Draw the node's plus/minus button according to the directionality. + if BidiMode = bdLeftToRight then + XPos := R.Left + ButtonX + else + XPos := R.Right - ButtonX - FPlusBM.Width; + + if (tsUseExplorerTheme in FStates) and not VclStyleEnabled then + begin + Glyph := IfThen(IsHot, TVP_HOTGLYPH, TVP_GLYPH); + State := IfThen(vsExpanded in Node.States, GLPS_OPENED, GLPS_CLOSED); + Pos := Rect(XPos, R.Top + ButtonY, XPos + FPlusBM.Width, R.Top + ButtonY + FPlusBM.Height); + Theme := OpenThemeData(Handle, 'TREEVIEW'); + DrawThemeBackground(Theme, Canvas.Handle, Glyph, State, Pos, nil); + CloseThemeData(Theme); + end + else + begin + if vsExpanded in Node.States then + begin + if IsHot then + begin + if IsSelected then + BitMap := FSelectedHotMinusBM + else + Bitmap := FHotMinusBM; + end + else + Bitmap := FMinusBM; + end + else + begin + if IsHot then + begin + if IsSelected then + BitMap := FSelectedHotPlusBM + else + Bitmap := FHotPlusBM; + end + else + Bitmap := FPlusBM; + end; + // Need to draw this masked. + Canvas.Draw(XPos, R.Top + ButtonY, Bitmap); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.PaintTreeLines(const PaintInfo: TVTPaintInfo; IndentSize: TDimension; const LineImage: TLineImage); + +var + I: Integer; + XPos, + Offset: TDimension; + NewStyles: TLineImage; + +begin + NewStyles := nil; + + with PaintInfo do + begin + if BidiMode = bdLeftToRight then + begin + XPos := CellRect.Left + PaintInfo.Offsets[ofsMargin]; + Offset := FIndent; + end + else + begin + Offset := -FIndent; + XPos := CellRect.Right - PaintInfo.Offsets[ofsMargin] + Offset; + end; + + case FLineMode of + lmBands: + if poGridLines in PaintInfo.PaintOptions then + begin + // Convert the line images in correct bands. + SetLength(NewStyles, Length(LineImage)); + for I := IndentSize - 1 downto 0 do + begin + if (vsExpanded in Node.States) and not (vsAllChildrenHidden in Node.States) then + NewStyles[I] := ltLeft + else + case LineImage[I] of + ltRight, + ltBottomRight, + ltTopDownRight, + ltTopRight: + NewStyles[I] := ltLeftBottom; + ltNone: + // Have to take over the image to the right of this one. A no line entry can never appear as + // last entry so I don't need an end check here. + if LineImage[I + 1] in [ltNone, ltTopRight] then + NewStyles[I] := NewStyles[I + 1] + else + NewStyles[I] := ltLeft; + ltTopDown: + // Have to check the image to the right of this one. A top down line can never appear as + // last entry so I don't need an end check here. + if LineImage[I + 1] in [ltNone, ltTopRight] then + NewStyles[I] := NewStyles[I + 1] + else + NewStyles[I] := ltLeft; + end; + end; + + PaintInfo.Canvas.Font.Color := FColors.GridLineColor; + for I := 0 to IndentSize - 1 do + begin + DoBeforeDrawLineImage(PaintInfo.Node, I + Ord(not (toShowRoot in TreeOptions.PaintOptions)), XPos); + DrawLineImage(PaintInfo, XPos, CellRect.Top, NodeHeight[Node] - 1, VAlign - 1, NewStyles[I], + BidiMode <> bdLeftToRight); + Inc(XPos, Offset); + end; + end; + else // lmNormal + PaintInfo.Canvas.Font.Color := FColors.TreeLineColor; + for I := 0 to IndentSize - 1 do + begin + DoBeforeDrawLineImage(PaintInfo.Node, I + Ord(not (toShowRoot in TreeOptions.PaintOptions)), XPos); + DrawLineImage(PaintInfo, XPos, CellRect.Top, NodeHeight[Node], VAlign - 1, LineImage[I], + BidiMode <> bdLeftToRight); + Inc(XPos, Offset); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.PaintSelectionRectangle(Target: TCanvas; WindowOrgX: TDimension; const SelectionRect: TRect; + TargetRect: TRect); + +// Helper routine to draw a selection rectangle in the mode determined by DrawSelectionMode. + +var + BlendRect: TRect; + TextColorBackup, + BackColorBackup: COLORREF; // used to restore forground and background colors when drawing a selection rectangle + +begin + if ((FDrawSelectionMode = smDottedRectangle) and not (tsUseThemes in FStates)) then + begin + // Classical selection rectangle using dotted borderlines. + TextColorBackup := GetTextColor(Target.Handle); + SetTextColor(Target.Handle, $FFFFFF); + BackColorBackup := GetBkColor(Target.Handle); + SetBkColor(Target.Handle, 0); + Target.DrawFocusRect(SelectionRect); + SetTextColor(Target.Handle, TextColorBackup); + SetBkColor(Target.Handle, BackColorBackup); + end + else + begin + // Modern alpha blended style. + OffsetRect(TargetRect, WindowOrgX, 0); + if IntersectRect(BlendRect, OrderRect(SelectionRect), TargetRect) then + begin + OffsetRect(BlendRect, -WindowOrgX, 0); + AlphaBlend(0, Target.Handle, BlendRect, Point(0, 0), bmConstantAlphaAndColor, FSelectionBlendFactor, + ColorToRGB(FColors.SelectionRectangleBlendColor)); + + Target.Brush.Color := FColors.SelectionRectangleBorderColor; + Target.FrameRect(SelectionRect); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: TDimension); + +// This method is called immediately before a cell's content is drawn und is responsible to paint selection colors etc. + +var + TextColorBackup, + BackColorBackup: COLORREF; + FocusRect, + InnerRect: TRect; + RowRect: TRect; + Theme: HTHEME; +const + TREIS_HOTSELECTED = 6; + + //--------------- local functions ------------------------------------------- + + procedure AlphaBlendSelection(Color: TColor); + + var + R: TRect; + + begin + // Take into account any window offset and size limitations in the target bitmap, as this is only as large + // as necessary and might not cover the whole node. For normal painting this does not matter (because of + // clipping) but for the MMX code there is no such check and it will crash badly when bitmap boundaries are + // crossed. + R := InnerRect; + OffsetRect(R, -WindowOrgX, 0); + if R.Left < 0 then + R.Left := 0; + if R.Right > MaxWidth then + R.Right := MaxWidth; + AlphaBlend(0, PaintInfo.Canvas.Handle, R, Point(0, 0), bmConstantAlphaAndColor, + FSelectionBlendFactor, ColorToRGB(Color)); + end; + + //--------------------------------------------------------------------------- + + procedure DrawBackground(State: Integer); + begin + // if the full row selection is disabled or toGridExtensions is in the MiscOptions, draw the selection + // into the InnerRect, otherwise into the RowRect + if not (toFullRowSelect in FOptions.SelectionOptions) or (toGridExtensions in FOptions.MiscOptions) then + DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, TVP_TREEITEM, State, InnerRect, nil) + else + DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, TVP_TREEITEM, State, RowRect, nil); + end; + + procedure DrawThemedFocusRect(State: Integer); + var + Theme: HTHEME; + begin + Theme := OpenThemeData(Application.ActiveFormHandle, 'Explorer::ItemsView'); + if not (toFullRowSelect in FOptions.SelectionOptions) or (toGridExtensions in FOptions.MiscOptions) then + DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, LVP_LISTDETAIL, State, InnerRect, nil) + else + DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, LVP_LISTDETAIL, State, RowRect, nil); + CloseThemeData(Theme); + end; + + //--------------- end local functions --------------------------------------- + +begin + if tsUseExplorerTheme in FStates then + begin + Theme := OpenThemeData(Application.ActiveFormHandle, 'Explorer::TreeView'); + RowRect := Rect(0, PaintInfo.CellRect.Top, FRangeX, PaintInfo.CellRect.Bottom); + if (Header.Columns.Count = 0) and (toFullRowSelect in TreeOptions.SelectionOptions) then + RowRect.Right := Max(ClientWidth, RowRect.Right); + if toShowVertGridLines in FOptions.PaintOptions then + Dec(RowRect.Right); + end; + + with PaintInfo, Canvas do + begin + // Fill cell background if its color differs from tree background. + with FHeader.Columns do + if poColumnColor in PaintOptions then + begin + Brush.Color := Items[Column].GetEffectiveColor; + FillRect(CellRect); + end; + + // Let the application customize the cell background and the content rectangle. + DoBeforeCellPaint(Canvas, Node, Column, cpmPaint, CellRect, ContentRect); + + InnerRect := ContentRect; + + // The selection rectangle depends on alignment. + if not (toGridExtensions in FOptions.MiscOptions) then + begin + case Alignment of + taLeftJustify: + if InnerRect.Left + NodeWidth < InnerRect.Right then + InnerRect.Right := InnerRect.Left + NodeWidth; + taCenter: + if (InnerRect.Right - InnerRect.Left) > NodeWidth then + begin + InnerRect.Left := Divide(InnerRect.Left + InnerRect.Right - NodeWidth, 2); + InnerRect.Right := InnerRect.Left + NodeWidth; + end; + taRightJustify: + if (InnerRect.Right - InnerRect.Left) > NodeWidth then + InnerRect.Left := InnerRect.Right - NodeWidth; + end; + end; + + if (Column = FFocusedColumn) or (toFullRowSelect in FOptions.SelectionOptions) then + begin + // Fill the selection rectangle. + if poDrawSelection in PaintOptions then + begin + if Node = FDropTargetNode then + begin + if (FLastDropMode = dmOnNode) or (vsSelected in Node.States) then + begin + Brush.Color := FColors.DropTargetColor; + Pen.Color := FColors.DropTargetBorderColor; + + if (toGridExtensions in FOptions.MiscOptions) or + (toFullRowSelect in FOptions.SelectionOptions) then + InnerRect := CellRect; + if not IsRectEmpty(InnerRect) then + if tsUseExplorerTheme in FStates then + DrawBackground(TREIS_SELECTED) + else + if (toUseBlendedSelection in FOptions.PaintOptions) then + AlphaBlendSelection(Brush.Color) + else + RoundRect(InnerRect.Left, InnerRect.Top, InnerRect.Right, InnerRect.Bottom, FSelectionCurveRadius, FSelectionCurveRadius); + end + else + begin + Brush.Style := bsClear; + end; + end + else + if vsSelected in Node.States then + begin + if Focused or (toPopupMode in FOptions.PaintOptions) then + begin + Brush.Color := FColors.FocusedSelectionColor; + Pen.Color := FColors.FocusedSelectionBorderColor; + end + else + begin + Brush.Color := FColors.UnfocusedSelectionColor; + Pen.Color := FColors.UnfocusedSelectionBorderColor; + end; + if (toGridExtensions in FOptions.MiscOptions) or (toFullRowSelect in FOptions.SelectionOptions) then + InnerRect := CellRect; + if not IsRectEmpty(InnerRect) then + if tsUseExplorerTheme in FStates then + begin + // If the node is also hot, its background will be drawn later. + if not (toHotTrack in FOptions.PaintOptions) or (Node <> FCurrentHotNode) or + ((Column <> FCurrentHotColumn) and not (toFullRowSelect in FOptions.SelectionOptions)) then + DrawBackground(IfThen(Self.Focused, TREIS_SELECTED, TREIS_SELECTEDNOTFOCUS)); + end + else + if (toUseBlendedSelection in FOptions.PaintOptions) then + AlphaBlendSelection(Brush.Color) + else + RoundRect(InnerRect.Left, InnerRect.Top, InnerRect.Right, InnerRect.Bottom, FSelectionCurveRadius, FSelectionCurveRadius); + end; + end; + end; + + if (tsUseExplorerTheme in FStates) and (toHotTrack in FOptions.PaintOptions) and (Node = FCurrentHotNode) and + ((Column = FCurrentHotColumn) or (toFullRowSelect in FOptions.SelectionOptions)) then + DrawBackground(IfThen((vsSelected in Node.States) and not (toAlwaysHideSelection in FOptions.PaintOptions), + TREIS_HOTSELECTED, TREIS_HOT)); + + if (Column = FFocusedColumn) or (toFullRowSelect in FOptions.SelectionOptions) then + begin + // draw focus rect + if (poDrawFocusRect in PaintOptions) and + (Focused or (toPopupMode in FOptions.PaintOptions)) and (FFocusedNode = Node) and + ( (Column = FFocusedColumn) or + (not (toExtendedFocus in FOptions.SelectionOptions) and + (toFullRowSelect in FOptions.SelectionOptions) and + (tsUseExplorerTheme in FStates) ) ) then + begin + TextColorBackup := GetTextColor(Handle); + SetTextColor(Handle, $FFFFFF); + BackColorBackup := GetBkColor(Handle); + SetBkColor(Handle, 0); + + if not (toExtendedFocus in FOptions.SelectionOptions) and (toFullRowSelect in FOptions.SelectionOptions) and + (tsUseExplorerTheme in FStates) then + FocusRect := RowRect + else + if toGridExtensions in FOptions.MiscOptions then + FocusRect := CellRect + else + FocusRect := InnerRect; + + if tsUseExplorerTheme in FStates then + InflateRect(FocusRect, -1, -1); + + if (tsUseExplorerTheme in FStates) then + begin + //Draw focused unselected style like Windows 7 Explorer + if not (vsSelected in Node.States) then + DrawThemedFocusRect(LIS_NORMAL) + else + DrawBackground(TREIS_HOTSELECTED); + end + else + Winapi.Windows.DrawFocusRect(Handle, FocusRect); + SetTextColor(Handle, TextColorBackup); + SetBkColor(Handle, BackColorBackup); + end; + end; + end; + + if tsUseExplorerTheme in FStates then + CloseThemeData(Theme); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +class procedure TBaseVirtualTree.RaiseVTError(const Msg: string; HelpContext: Integer); + +begin + raise EVirtualTreeError.CreateHelp(Msg, HelpContext); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType, + ChunkSize: Integer): Boolean; + +// Called while loading a tree structure, Node is already valid (allocated) at this point. +// The function handles the base and user chunks, any other chunk is marked as being unknown (result becomes False) +// and skipped. descendants may handle them by overriding this method. +// Returns True if the chunk could be handled, otherwise False. +type + TAdvancedVersion2Identifier = packed record + ChildCount: Cardinal; + NodeHeight: TDimension; + States: Word; + Align: Byte; + CheckState: TCheckState; + CheckType: TCheckType; + Reserved: Cardinal; + end; + +var + IdBody: TAdvancedVersion2Identifier; + ChunkBody: TBaseChunkBody; + Run: PVirtualNode; + LastPosition: Integer; + +begin + case ChunkType of + BaseChunk: + begin + // Load base chunk's body (chunk header has already been consumed). + case Version of + 1: + begin + with ChunkBody do + begin + // In version prior to 2 there was a smaller chunk body. Hence we have to read it entry by entry now. + Stream.Read(ChildCount, SizeOf(ChildCount)); + Stream.Read(NodeHeight, SizeOf(NodeHeight)); + // TVirtualNodeStates was a byte sized type in version 1. + States := []; + Stream.Read(States, SizeOf(Byte)); + // vsVisible is now in the place where vsSelected was before, but every node was visible in the old version + // so we need to fix this too. + if vsVisible in States then + //sync path note: prior version stream reading, ignored for syncing + Include(States, vsSelected) + else + Include(States, vsVisible); + Stream.Read(Align, SizeOf(Align)); + Stream.Read(CheckState, SizeOf(CheckState)); + Stream.Read(CheckType, SizeOf(CheckType)); + end; + end; + 2: + begin + ZeroMemory(@IdBody, SizeOf(IdBody)); + Stream.Read(IdBody, SizeOf(IdBody)); + // If Align is greater than zero, we have a stream prior to VT version 6.2 + if IdBody.Align > 0 then + with ChunkBody do + begin + ChildCount := IdBody.ChildCount; + NodeHeight := IdBody.NodeHeight; + States := []; + System.Move(IdBody.States, States, SizeOf(IdBody.States)); + CheckState := IdBody.CheckState; + CheckType := IdBody.CheckType; + Reserved := IdBody.Reserved; + end + else + begin + // Stream is compatible with current size of TBaseChunkBody + Stream.Position := Stream.Position - SizeOf(IdBody); + Stream.Read(ChunkBody, SizeOf(ChunkBody)); + end; + end; + 3: + Stream.Read(ChunkBody, SizeOf(ChunkBody)); + end; + + with Node^ do + begin + // Set states first, in case the node is invisible. + States := ChunkBody.States; + SetNodeHeight(ChunkBody.NodeHeight); + TotalHeight := NodeHeight; + Align := ChunkBody.Align; + CheckState := ChunkBody.CheckState; + CheckType := ChunkBody.CheckType; + SetChildCount(ChunkBody.ChildCount); + + // Create and read child nodes. + while ChunkBody.ChildCount > 0 do + begin + Run := MakeNewNode; + + Run.SetPrevSibling(Node.LastChild); + if Assigned(Run.PrevSibling) then + Run.SetIndex(Run.PrevSibling.Index + 1); + if Assigned(Node.LastChild) then + Node.LastChild.SetNextSibling(Run) + else + Node.SetFirstChild(Run); + Node.SetLastChild(Run); + Run.SetParent(Node); + + ReadNode(Stream, Version, Run); + System.Dec(ChunkBody.ChildCount); + end; + end; + Result := True; + end; + UserChunk: + if ChunkSize > 0 then + begin + // need to know whether the data was read + LastPosition := Stream.Position; + DoLoadUserData(Node, Stream); + // compare stream position to learn whether the data was read + Result := Stream.Position > LastPosition; + // Improve stability by advancing the stream to the chunk's real end if + // the application did not read what has been written. + if not Result or (Stream.Position <> (LastPosition + ChunkSize)) then + Stream.Position := LastPosition + ChunkSize; + end + else + Result := True; + else + // unknown chunk, skip it + Stream.Position := Stream.Position + ChunkSize; + Result := False; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ReadNode(Stream: TStream; Version: Integer; Node: PVirtualNode); + +// Reads the anchor chunk of each node and initiates reading the sub chunks for this node + +var + Header: TChunkHeader; + EndPosition: Integer; + +begin + with Stream do + begin + // Read anchor chunk of the node. + Stream.Read(Header, SizeOf(Header)); + if Header.ChunkType = NodeChunk then + begin + EndPosition := Stream.Position + Header.ChunkSize; + // Read all subchunks until the indicated chunk end position is reached in the stream. + while Position < EndPosition do + begin + // Read new chunk header. + Stream.Read(Header, SizeOf(Header)); + ReadChunk(Stream, Version, Node, Header.ChunkType, Header.ChunkSize); + end; + // If the last chunk does not end at the given end position then there is something wrong. + if Position <> EndPosition then + RaiseVTError(SCorruptStream2, hcTFCorruptStream2); + end + else + RaiseVTError(SCorruptStream1, hcTFCorruptStream1); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.RedirectFontChangeEvent(Canvas: TCanvas); + +begin + if @Canvas.Font.OnChange <> @FOldFontChange then + begin + FOldFontChange := Canvas.Font.OnChange; + Canvas.Font.OnChange := FontChanged; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.RemoveFromSelection(Node: PVirtualNode); + +var + Index: Integer; + +begin + if not FSelectionLocked then + begin + Assert(Assigned(Node), 'Node must not be nil!'); + Assert(GetCurrentThreadId = MainThreadId, Self.Classname + '.RemoveFromSelection() must only be called from UI thread.'); + if vsSelected in Node.States then + begin + Assert(FSelectionCount > 0, 'if one node has set the vsSelected flag, SelectionCount must be >0.'); + //sync path note: deselect when a ctrl click removes a selection + Exclude(Node.States, vsSelected); + if SyncCheckstateWithSelection[Node] then + Node.CheckState := csUncheckedNormal; // Avoid using SetCheckState() as it handles toSyncCheckboxesWithSelection as well. + + if FindNodeInSelection(Node, Index, -1, -1) and (Index < FSelectionCount - 1) then + System.Move(FSelection[Index + 1], FSelection[Index], (FSelectionCount - Index - 1) * SizeOf(Pointer)); + if FSelectionCount > 0 then + System.Dec(FSelectionCount); + SetLength(FSelection, FSelectionCount); + + if FSelectionCount = 0 then + ResetRangeAnchor; + + if FSelectionCount <= 1 then + UpdateNextNodeToSelect(Node); + + DoRemoveFromSelection(Node); + InvalidateNode(Node); + Change(Node); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateNextNodeToSelect(Node: PVirtualNode); + +// save a potential node to select after the currently selected node will be deleted. +// This will make the VT to behave more like the Win32 TreeView, which always selecta a new node if the currently +// selected one gets deleted. + +begin + if ([toAlwaysSelectNode, toSelectNextNodeOnRemoval] * TreeOptions.SelectionOptions) = [] then + Exit; + if GetNextSibling(Node) <> nil then + FNextNodeToSelect := GetNextSibling(Node) + else if GetPreviousSibling(Node) <> nil then + FNextNodeToSelect := GetPreviousSibling(Node) + else if Node.Parent <> FRoot then + FNextNodeToSelect := Node.Parent + else + FNextNodeToSelect := nil; +end;//if Assigned(Node); + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ResetRangeAnchor; + +// Called when there is no selected node anymore and the selection range anchor needs a new value. + +begin + FRangeAnchor := FFocusedNode; + FLastSelectionLevel := -1; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.RestoreFontChangeEvent(Canvas: TCanvas); + +begin + Canvas.Font.OnChange := FOldFontChange; + FOldFontChange := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SelectNodes(StartNode, EndNode: PVirtualNode; AddOnly: Boolean); + +// Selects a range of nodes and unselects all other eventually selected nodes which are not in this range if +// AddOnly is False. +// EndNode must be visible while StartNode does not necessarily as in the case where the last focused node is the start +// node but it is a child of a node which has been collapsed previously. In this case the first visible parent node +// is used as start node. StartNode can be nil in which case the very first node in the tree is used. + +var + NodeFrom, + NodeTo, + LastAnchor: PVirtualNode; + Index: Integer; + +begin + Assert(Assigned(EndNode), 'EndNode must not be nil!'); + if not FSelectionLocked then + begin + ClearTempCache; + if StartNode = nil then + StartNode := GetFirstVisibleNoInit(nil, True) + else + if not FullyVisible[StartNode] then + begin + StartNode := GetPreviousVisible(StartNode, True); + if StartNode = nil then + StartNode := GetFirstVisibleNoInit(nil, True); + end; + + if CompareNodePositions(StartNode, EndNode, True) < 0 then + begin + NodeFrom := StartNode; + NodeTo := EndNode; + end + else + begin + NodeFrom := EndNode; + NodeTo := StartNode; + end; + + // The range anchor will be reset by the following call. + LastAnchor := FRangeAnchor; + if not AddOnly then + InternalClearSelection; + + while NodeFrom <> NodeTo do + begin + InternalCacheNode(NodeFrom); + NodeFrom := GetNextVisible(NodeFrom, True); + end; + // select last node too + InternalCacheNode(NodeFrom); + // now add them all in "one" step + AddToSelection(FTempNodeCache, FTempNodeCount); + ClearTempCache; + if Assigned(LastAnchor) and FindNodeInSelection(LastAnchor, Index, -1, -1) then + FRangeAnchor := LastAnchor; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SetFocusedNodeAndColumn(Node: PVirtualNode; Column: TColumnIndex); + +var + OldColumn: TColumnIndex; + WasDifferent: Boolean; + +begin + if not FHeader.AllowFocus(Column) then + Column := FFocusedColumn; + + WasDifferent := (Node <> FFocusedNode) or (Column <> FFocusedColumn); + + OldColumn := FFocusedColumn; + FFocusedColumn := Column; + + DoFocusNode(Node, True); + + // Check if the change was accepted. + if FFocusedNode = Node then + begin + CancelEditNode; + if WasDifferent then + DoFocusChange(FFocusedNode, FFocusedColumn); + end + else + // If the user did not accept the new cell to focus then set also the focused column back + // to its original state. + FFocusedColumn := OldColumn; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SkipNode(Stream: TStream); + +// Skips the data for the next node in the given stream (including the child nodes). + +var + Header: TChunkHeader; + +begin + with Stream do + begin + // read achor chunk of the node + Stream.Read(Header, SizeOf(Header)); + if Header.ChunkType = NodeChunk then + Stream.Position := Stream.Position + Header.ChunkSize + else + RaiseVTError(SCorruptStream1, hcTFCorruptStream1); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.StartWheelPanning(Position: TPoint); + +// Called when wheel panning should start. A little helper window is created to indicate the reference position, +// which determines in which direction and how far wheel panning/scrolling will happen. + + //--------------- local function -------------------------------------------- + function CreatePanningWindow(const ImageName: TPanningCursor; const Pos: TPoint): TForm; + var + Form: TForm; + Image: TImage; + PanningImage: TIcon; + begin + Form := TForm.Create(Self); + Form.PopupMode := pmExplicit; + Form.PopupParent := GetParentForm(Self); + Form.TransparentColor := True; + Form.TransparentColorValue := clBtnFace; + Form.Width := ScaledPixels(32); + Form.Height := Form.Width; + Form.BorderStyle := bsNone; + Form.StyleElements := []; + Image := TImage.Create(Form); + Image.Left := 0; + Image.Top := 0; + Image.Parent := Form; + Image.Align := TAlign.alClient; + + PanningImage := TIcon.Create; + try + PanningImage.Handle := LoadImage(0, MAKEINTRESOURCE(ImageName), IMAGE_CURSOR, Form.Width, Form.Height, LR_DEFAULTCOLOR or LR_LOADTRANSPARENT); + Image.Picture.Assign(PanningImage); + Form.Left := Pos.X - (PanningImage.Width div 2); + Form.Top := Pos.Y - (PanningImage.Height div 2); + finally + PanningImage.Free; + end; + Form.Position := poDesigned; + // This prevents a focus chnage compare to using TForm.Show() + ShowWindow(Form.Handle, SW_SHOWNOACTIVATE); + Form.Visible := True; + Exit(Form); + end; + //--------------- end local function ---------------------------------------- + +var + ImageName: TPanningCursor; + Pt: TPoint; + +begin + StopTimer(ScrollTimer); + DoStateChange([tsPanning]); + + // Determine correct cursor + if FRangeX > ClientWidth then + begin + if FRangeY > ClientHeight then + ImageName := TPanningCursor.MOVEALL + else + ImageName := TPanningCursor.MOVEEW; + end + else + ImageName := TPanningCursor.MOVENS; + + // Create the helper window and show it at the given position without activating it. + Pt := ClientToScreen(Position); + FPanningWindow := CreatePanningWindow(ImageName, Pt); + + // Setup the panscroll timer and capture all mouse input. + TrySetFocus(); + SetCapture(Handle); + SetTimer(Handle, ScrollTimer, 20, nil); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.StopWheelPanning; + +// Stops panning if currently active and destroys the helper window. + +begin + if tsPanning in FStates then + begin + // Release the mouse capture and stop the panscroll timer. + StopTimer(ScrollTimer); + ReleaseCapture; + DoStateChange([], [tsPanning]); + + // Destroy the helper window. + if Assigned(FPanningWindow) then + FPanningWindow.Release; + DeleteObject(FPanningCursor); + FPanningCursor := 0; + Winapi.Windows.SetCursor(Screen.Cursors[Cursor]); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.StructureChange(Node: PVirtualNode; Reason: TChangeReason); + +begin + AdviseChangeEvent(True, Node, Reason); + + if FUpdateCount = 0 then + begin + if (FChangeDelay > 0) and HandleAllocated and not (tsSynchMode in FStates) then + SetTimer(Handle, StructureChangeTimer, FChangeDelay, nil) + else + DoStructureChange(Node, Reason); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.StyleServices(AControl: TControl): TCustomStyleServices; +begin + if AControl = nil then + AControl := Self; + Result := VTStyleServices(AControl); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.SuggestDropEffect(Source: TObject; Shift: TShiftState; Pt: TPoint; + AllowedEffects: Integer): Integer; + +// determines the drop action to take if the drag'n drop operation ends on this tree +// Note: Source can be any Delphi object not just a virtual tree + +begin + Result := AllowedEffects; + + // prefer MOVE if source and target are the same control, otherwise whatever is allowed as initial value + if Assigned(Source) and (Source = Self) then + if (AllowedEffects and DROPEFFECT_MOVE) <> 0 then + Result := DROPEFFECT_MOVE + else // no change + else + // drag between different applicatons + if (AllowedEffects and DROPEFFECT_COPY) <> 0 then + Result := DROPEFFECT_COPY; + + // consider modifier keys and what is allowed at the moment, if none of the following conditions apply then + // the initial value just set is used + if ssCtrl in Shift then + begin + // copy or link + if ssShift in Shift then + begin + // link + if (AllowedEffects and DROPEFFECT_LINK) <> 0 then + Result := DROPEFFECT_LINK; + end + else + begin + // copy + if (AllowedEffects and DROPEFFECT_COPY) <> 0 then + Result := DROPEFFECT_COPY; + end; + end + else + begin + // move, link or default + if ssShift in Shift then + begin + // move + if (AllowedEffects and DROPEFFECT_MOVE) <> 0 then + Result := DROPEFFECT_MOVE; + end + else + begin + // link or default + if ssAlt in Shift then + begin + // link + if (AllowedEffects and DROPEFFECT_LINK) <> 0 then + Result := DROPEFFECT_LINK; + end; + // else default + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ToggleSelection(StartNode, EndNode: PVirtualNode); + +// Switchs the selection state of a range of nodes. +// Note: This method is specifically designed to help selecting ranges with the keyboard and considers therefore +// the range anchor. + +var + NodeFrom, + NodeTo: PVirtualNode; + NewSize: Integer; + Position: Integer; + +begin + if not FSelectionLocked then + begin + Assert(Assigned(EndNode), 'EndNode must not be nil!'); + if StartNode = nil then + StartNode := FRoot.FirstChild + else + if not FullyVisible[StartNode] then + StartNode := GetPreviousVisible(StartNode, True); + + Position := CompareNodePositions(StartNode, EndNode); + // nothing to do if start and end node are the same + if Position <> 0 then + begin + if Position < 0 then + begin + NodeFrom := StartNode; + NodeTo := EndNode; + end + else + begin + NodeFrom := EndNode; + NodeTo := StartNode; + end; + + ClearTempCache; + + // 1) toggle the start node if it is before the range anchor + if CompareNodePositions(NodeFrom, FRangeAnchor) < 0 then + if not (vsSelected in NodeFrom.States) then + InternalCacheNode(NodeFrom) + else + InternalRemoveFromSelection(NodeFrom); + + // 2) toggle all nodes within the range + NodeFrom := GetNextVisible(NodeFrom, True); + while NodeFrom <> NodeTo do + begin + if not (vsSelected in NodeFrom.States) then + InternalCacheNode(NodeFrom) + else + InternalRemoveFromSelection(NodeFrom); + NodeFrom := GetNextVisible(NodeFrom, True); + end; + + // 3) toggle end node if it is after the range anchor + if CompareNodePositions(NodeFrom, FRangeAnchor) > 0 then + if not (vsSelected in NodeFrom.States) then + InternalCacheNode(NodeFrom) + else + InternalRemoveFromSelection(NodeFrom); + + // Do some housekeeping if there was a change. + NewSize := PackArray(FSelection, FSelectionCount); + if NewSize > -1 then + begin + FSelectionCount := NewSize; + SetLength(FSelection, FSelectionCount); + end; + // If the range went over the anchor then we need to reselect it. + if not (vsSelected in FRangeAnchor.States) then + InternalCacheNode(FRangeAnchor); + if FTempNodeCount > 0 then + AddToSelection(FTempNodeCache, FTempNodeCount); + ClearTempCache; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.TrySetFocus(); +begin + if Visible and CanFocus then + begin + try + Self.SetFocus(); + except + on EInvalidOperation do + Exit; + end; + end;//if +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UnselectNodes(StartNode, EndNode: PVirtualNode); + +// Deselects a range of nodes. +// EndNode must be visible while StartNode must not as in the case where the last focused node is the start node +// but it is a child of a node which has been collapsed previously. In this case the first visible parent node +// is used as start node. StartNode can be nil in which case the very first node in the tree is used. + +var + NodeFrom, + NodeTo: PVirtualNode; + NewSize: Integer; + +begin + if not FSelectionLocked then + begin + Assert(Assigned(EndNode), 'EndNode must not be nil!'); + + if StartNode = nil then + StartNode := FRoot.FirstChild + else + if not FullyVisible[StartNode] then + begin + StartNode := GetPreviousVisible(StartNode, True); + if StartNode = nil then + StartNode := FRoot.FirstChild; + end; + + if CompareNodePositions(StartNode, EndNode) < 0 then + begin + NodeFrom := StartNode; + NodeTo := EndNode; + end + else + begin + NodeFrom := EndNode; + NodeTo := StartNode; + end; + + while NodeFrom <> NodeTo do + begin + InternalRemoveFromSelection(NodeFrom); + NodeFrom := GetNextVisible(NodeFrom, True); + end; + // Deselect last node too. + InternalRemoveFromSelection(NodeFrom); + + // Do some housekeeping. + NewSize := PackArray(FSelection, FSelectionCount); + if NewSize > -1 then + begin + FSelectionCount := NewSize; + SetLength(FSelection, FSelectionCount); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateColumnCheckState(Col: TVirtualTreeColumn); +var + NewCheckState: TCheckState; +begin + NewCheckState := DetermineNextCheckState(Col.CheckType, Col.CheckState); + if (Col.CheckState <> NewCheckState) and DoColumnChecking(Col.Index, NewCheckState) then + begin + Col.CheckState := NewCheckState; + DoColumnChecked(Col.Index); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateDesigner; + +var + ParentForm: TCustomForm; + +begin + if (csDesigning in ComponentState) and not (csUpdating in ComponentState) then + begin + ParentForm := GetParentForm(Self); + if Assigned(ParentForm) and Assigned(ParentForm.Designer) then + ParentForm.Designer.Modified; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateHeaderRect(); + +// Calculates the rectangle the header occupies in non-client area. +// These coordinates are in window rectangle. + +var + OffsetX, + OffsetY: TDimension; + EdgeSize: TDimension; + Size: TSize; + +begin + FHeaderRect := Rect(0, 0, Width, Height); + + // Consider borders... + if HandleAllocated then begin // Prevent preliminary creation of window handle, see issue #933 + Size := GetBorderDimensions(); + InflateRect(FHeaderRect, Size.cx, Size.cy); + end; + + // ... and bevels. + OffsetX := BorderWidth; + OffsetY := BorderWidth; + if BevelKind <> TBevelKind.bkNone then + begin + EdgeSize := 0; + if BevelInner <> TBevelCut.bvNone then + Inc(EdgeSize, BevelWidth); + if BevelOuter <> TBevelCut.bvNone then + Inc(EdgeSize, BevelWidth); + if TBevelEdge.beLeft in BevelEdges then + Inc(OffsetX, EdgeSize); + if TBevelEdge.beTop in BevelEdges then + Inc(OffsetY, EdgeSize); + end; + + InflateRect(FHeaderRect, -OffsetX, -OffsetY); + + if hoVisible in FHeader.Options then + begin + if FHeaderRect.Left <= FHeaderRect.Right then + FHeaderRect.Bottom := FHeaderRect.Top + FHeader.Height + else + FHeaderRect := Rect(0, 0, 0, 0); + end + else + FHeaderRect.Bottom := FHeaderRect.Top; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateEditBounds; + +// Used to update the bounds of the current node editor if editing is currently active. + +var + R: TRect; + CurrentAlignment: TAlignment; + CurrentBidiMode: TBidiMode; + offsets : TVTOffsets; + offset : TDimension; + +begin + if (tsEditing in FStates) and Assigned(FFocusedNode) and + (FEditColumn < FHeader.Columns.Count) then // prevent EArgumentOutOfRangeException + begin + if (GetCurrentThreadId <> MainThreadID) then + begin + // UpdateEditBounds() will be called at the end of the thread + Exit; + end; + if vsMultiline in FFocusedNode.States then + R := GetDisplayRect(FFocusedNode, FEditColumn, True, False) + else if not (toGridExtensions in FOptions.MiscOptions) then + R := GetDisplayRect(FFocusedNode, FEditColumn, True, True); + + if (toGridExtensions in FOptions.MiscOptions) then + begin + // Use the whole cell when grid extensions are on. + R := GetDisplayRect(FFocusedNode, FEditColumn, False, False); + if FEditColumn = FHeader.MainColumn then + begin + // Calculate an offset for the main column. + GetOffsets(FFocusedNode, offsets, ofsLabel, FEditColumn); + offset := offsets[ofsLabel]; +// if offsets[ofsToggleButton] < 0 then +// Inc(offset, offsets[ofsToggleButton]); + end + else + offset := 0; + + // Adjust edit bounds depending on alignment and bidi mode. + if FEditColumn <= NoColumn then + begin + CurrentAlignment := Alignment; + CurrentBidiMode := BiDiMode; + end + else + begin + CurrentAlignment := FHeader.Columns[FEditColumn].Alignment; + CurrentBidiMode := FHeader.Columns[FEditColumn].BiDiMode; + end; + // Consider bidi mode here. In RTL context does left alignment actually mean right alignment and vice versa. + if CurrentBidiMode <> bdLeftToRight then + ChangeBiDiModeAlignment(CurrentAlignment); + if CurrentAlignment = taLeftJustify then + begin + if CurrentBiDiMode = bdLeftToRight then + Inc(R.Left, offset) + else + Dec(R.Right, offset); + end + else + begin + if CurrentBiDiMode = bdLeftToRight then + Inc(R.Left, offset) + else + Dec(R.Right, offset); + end; + end; + if toShowHorzGridLines in TreeOptions.PaintOptions then + Dec(R.Bottom); + R.Bottom := R.Top + R.Bottom - R.Top; + FEditLink.SetBounds(R); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +const + ScrollMasks: array[Boolean] of Cardinal = (0, SIF_DISABLENOSCROLL); + +const // Region identifiers for GetRandomRgn + CLIPRGN = 1; + METARGN = 2; + APIRGN = 3; + SYSRGN = 4; + +function GetRandomRgn(DC: HDC; Rgn: HRGN; iNum: Integer): Integer; stdcall; external 'GDI32.DLL'; + +procedure TBaseVirtualTree.ValidateCache(); + +// Starts cache validation if not already done by adding this instance to the worker thread's waiter list +// (if not already there) and signalling the thread it can start validating. + +begin + // stop validation if it is currently validating this tree's cache. + InterruptValidation(); + + FStartIndex := 0; + if (tsValidationNeeded in FStates) and (FVisibleCount > CacheThreshold) then + begin + // Tell the thread this tree needs actually something to do. + TWorkerThread.AddTree(Self); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ValidateNodeDataSize(var Size: Integer); + +begin + Size := SizeOf(Pointer); + if Assigned(FOnGetNodeDataSize) then + FOnGetNodeDataSize(Self, Size); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.VclStyleChanged(); + + // Updates the member FVclStyleEnabled, should be called initially and when the VCL style changes + +begin + FVclStyleEnabled := StyleServices.Enabled and not StyleServices.IsSystemStyle {$IF CompilerVersion < 35} and not (csDesigning in ComponentState) {$ifend}; + Header.StyleChanged(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +//PROFILE-NO +procedure TBaseVirtualTree.WndProc(var Message: TMessage); + +var + Handled: Boolean; + +begin + Handled := False; + + // Try the header whether it needs to take this message. + if Assigned(FHeader) and (FHeader.States <> []) then + Handled := TVTHeaderCracker(FHeader).HandleMessage(Message); + if not Handled then + begin + // For auto drag mode, let tree handle itself, instead of TControl. + if not (csDesigning in ComponentState) and + ((Message.Msg = WM_LBUTTONDOWN) or (Message.Msg = WM_LBUTTONDBLCLK)) then + begin + if (DragMode = dmAutomatic) and (DragKind = dkDrag) then + begin + if IsControlMouseMsg(TWMMouse(Message)) then + Handled := True; + if not Handled then + begin + ControlState := ControlState + [csLButtonDown]; + Dispatch(Message); // overrides TControl's BeginDrag + Handled := True; + end; + end; + end; + + if not Handled and Assigned(FHeader) then + Handled := TVTHeaderCracker(FHeader).HandleMessage(Message); + + if not Handled then + begin + if (Message.Msg in [WM_NCLBUTTONDOWN, WM_NCRBUTTONDOWN, WM_NCMBUTTONDOWN]) and not Focused then + TrySetFocus; + inherited; + end; + end; +end; +//PROFILE-YES + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WriteChunks(Stream: TStream; Node: PVirtualNode); + +// Writes the core chunks for Node into the stream. +// Note: descendants can optionally override this method to add other node specific chunks. +// Keep in mind that this method is also called for the root node. Using this fact in descendants you can +// create a kind of "global" chunks not directly bound to a specific node. + +var + Header: TChunkHeader; + LastPosition, + ChunkSize: Integer; + Chunk: TBaseChunk; + Run: PVirtualNode; + +begin + with Stream do + begin + // 1. The base chunk... + LastPosition := Position; + Chunk.Header.ChunkType := BaseChunk; + with Node^, Chunk do + begin + Body.ChildCount := ChildCount; + Body.NodeHeight := NodeHeight; + // Some states are only temporary so take them out as they make no sense at the new location. + Body.States := States - [vsChecking, vsCutOrCopy, vsDeleting, vsOnFreeNodeCallRequired, vsHeightMeasured]; + Body.Align := Align; + Body.CheckState := GetCheckState(Node); + Body.CheckType := CheckType; + Body.Reserved := 0; + end; + // write the base chunk + Write(Chunk, SizeOf(Chunk)); + + // 2. ... directly followed by the child node chunks (actually they are child chunks of + // the base chunk) + if vsInitialized in Node.States then + begin + Run := Node.FirstChild; + while Assigned(Run) do + begin + WriteNode(Stream, Run); + Run := Run.NextSibling; + end; + end; + + FinishChunkHeader(Stream, LastPosition, Position); + + // 3. write user data + LastPosition := Position; + Header.ChunkType := UserChunk; + Write(Header, SizeOf(Header)); + DoSaveUserData(Node, Stream); + // check if the application actually wrote data + ChunkSize := Position - LastPosition - SizeOf(TChunkHeader); + // seek back to start of chunk if nothing has been written + if ChunkSize = 0 then + begin + Position := LastPosition; + Size := Size - SizeOf(Header); + end + else + FinishChunkHeader(Stream, LastPosition, Position); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.WriteNode(Stream: TStream; Node: PVirtualNode); + +// Writes the "cover" chunk for Node to Stream and initiates writing child nodes and chunks. + +var + LastPosition: Integer; + Header: TChunkHeader; + +begin + // Initialize the node first if necessary and wanted. + if toInitOnSave in FOptions.MiscOptions then + begin + if not (vsInitialized in Node.States) then + InitNode(Node); + if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then + InitChildren(Node); + end; + + with Stream do + begin + LastPosition := Position; + // Emit the anchor chunk. + Header.ChunkType := NodeChunk; + Write(Header, SizeOf(Header)); + // Write other chunks to stream taking their size into this chunk's size. + WriteChunks(Stream, Node); + + // Update chunk size. + FinishChunkHeader(Stream, LastPosition, Position); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.AbsoluteIndex(Node: PVirtualNode): Cardinal; + +begin + Result := 0; + while Assigned(Node) and (Node <> FRoot) do + begin + if not (vsInitialized in Node.States) then + InitNode(Node); + if Assigned(Node.PrevSibling) then + begin + // if there's a previous sibling then add its total count to the result + Node := Node.PrevSibling; + System.Inc(Result, Node.TotalCount); + end + else + begin + Node := Node.Parent; + if Node <> FRoot then + System.Inc(Result); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.AddChild(Parent: PVirtualNode; UserData: Pointer = nil): PVirtualNode; + +// Adds a new node to the given parent node. This is simply done by increasing the child count of the +// parent node. If Parent is nil then the new node is added as (last) top level node. +// UserData can be used to set the first SizeOf(Pointer) bytes of the user data area to an initial value which can be used +// in OnInitNode and will also cause to trigger the OnFreeNode event (if <> nil) even if the node is not yet +// "officially" initialized. +// AddChild is a compatibility method and will implicitly validate the parent node. This is however +// against the virtual paradigm and hence I dissuade from its usage. + +begin + if not (toReadOnly in FOptions.MiscOptions) then + Result := InsertNode(Parent, TVTNodeAttachMode.amAddChildLast, UserData) + else + Result := nil; +end; + +function TBaseVirtualTree.AddChild(Parent: PVirtualNode; const UserData: IInterface): PVirtualNode; +begin + UserData._AddRef(); + Result := AddChild(Parent, Pointer(UserData)); + Include(Result.States, vsReleaseCallOnUserDataRequired); +end; + +function TBaseVirtualTree.AddChild(Parent: PVirtualNode; const UserData: TObject): PVirtualNode; +begin + Result := AddChild(Parent, Pointer(UserData)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.AddFromStream(Stream: TStream; TargetNode: PVirtualNode); + +// loads nodes from the given stream and adds them to TargetNode +// the current content is not cleared before the load process starts (see also LoadFromStream) + +var + ThisID: TMagicID; + Version, + Count: Cardinal; + Node: PVirtualNode; + +begin + if not (toReadOnly in FOptions.MiscOptions) then + begin + // check first whether this is a stream we can read + Stream.ReadBuffer(ThisID, SizeOf(TMagicID)); + if (ThisID[0] = MagicID[0]) and + (ThisID[1] = MagicID[1]) and + (ThisID[2] = MagicID[2]) and + (ThisID[5] = MagicID[5]) then + begin + Version := Word(ThisID[3]); + if Version <= VTTreeStreamVersion then + begin + BeginUpdate; + try + if Version < 2 then + Count := MaxInt + else + Stream.ReadBuffer(Count, SizeOf(Count)); + + while (Stream.Position < Stream.Size) and (Count > 0) do + begin + System.Dec(Count); + Node := MakeNewNode; + InternalConnectNode(Node, TargetNode, Self, amAddChildLast); + InternalAddFromStream(Stream, Version, Node); + end; + if TargetNode = FRoot then + DoNodeCopied(nil) + else + DoNodeCopied(TargetNode); + finally + EndUpdate; + end; + end + else + RaiseVTError(SWrongStreamVersion, hcTFWrongStreamVersion); + end + else + RaiseVTError(SWrongStreamVersion, hcTFWrongStreamVersion); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.AfterConstruction; + +begin + inherited; + + if FRoot = nil then + InitRootNode; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.Assign(Source: TPersistent); + +begin + if (Source is TBaseVirtualTree) and not (toReadOnly in FOptions.MiscOptions) then + with Source as TBaseVirtualTree do + begin + Self.Align := Align; + Self.Anchors := Anchors; + Self.AutoScrollDelay := AutoScrollDelay; + Self.AutoScrollInterval := AutoScrollInterval; + Self.AutoSize := AutoSize; + Self.Background := Background; + Self.BevelEdges := BevelEdges; + Self.BevelInner := BevelInner; + Self.BevelKind := BevelKind; + Self.BevelOuter := BevelOuter; + Self.BevelWidth := BevelWidth; + Self.BiDiMode := BiDiMode; + Self.BorderStyle := BorderStyle; + Self.BorderWidth := BorderWidth; + Self.ChangeDelay := ChangeDelay; + Self.CheckImageKind := CheckImageKind; + Self.Color := Color; + Self.Colors.Assign(Colors); + Self.Constraints.Assign(Constraints); + Self.Ctl3D := Ctl3D; + Self.DefaultNodeHeight := DefaultNodeHeight; + Self.DefaultPasteMode := DefaultPasteMode; + Self.DragCursor := DragCursor; + Self.DragImageKind := DragImageKind; + Self.DragKind := DragKind; + Self.DragMode := DragMode; + Self.Enabled := Enabled; + Self.Font := Font; + Self.Header := Header; + Self.HintMode := HintMode; + Self.HotCursor := HotCursor; + Self.Images := Images; + Self.ImeMode := ImeMode; + Self.ImeName := ImeName; + Self.Indent := Indent; + Self.Margin := Margin; + Self.NodeAlignment := NodeAlignment; + Self.NodeDataSize := NodeDataSize; + Self.TreeOptions := TreeOptions; + Self.ParentBiDiMode := ParentBiDiMode; + Self.ParentColor := ParentColor; + Self.ParentCtl3D := ParentCtl3D; + Self.ParentFont := ParentFont; + Self.ParentShowHint := ParentShowHint; + Self.PopupMenu := PopupMenu; + Self.RootNodeCount := RootNodeCount; + Self.ScrollBarOptions := ScrollBarOptions; + Self.ShowHint := ShowHint; + Self.StateImages := StateImages; + Self.StyleElements := StyleElements; + Self.TabOrder := TabOrder; + Self.TabStop := TabStop; + Self.Visible := Visible; + Self.SelectionCurveRadius := SelectionCurveRadius; + Self.SelectionBlendFactor := SelectionBlendFactor; + Self.EmptyListMessage := EmptyListMessage; + end + else + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.AutoScale(); + +// If toAutoChangeScale is set, this method ensures that the default node height is set correctly. + +var + lTextHeight: TDimension; +begin + if HandleAllocated and (toAutoChangeScale in TreeOptions.AutoOptions) then + begin + Canvas.Font.Assign(Self.Font); + lTextHeight := Canvas.TextHeight('Tg') + TextMargin; + if Assigned(Images) then + lTextHeight := Max(lTextHeight, Images.Height + IfThen(fImagesMargin > 1, fImagesMargin div 2, fImagesMargin)); // ImagesMargin is the distance between two Images / checboxes. Don't count it twice vertically => div 2 + // By default, we only ensure that DefaultNodeHeight is large enough. + // If the form's dpi has changed, we scale up and down the DefaultNodeHeight, See issue #677. + if (lTextHeight <> Self.DefaultNodeHeight) then begin + ScaleNodeHeights(lTextHeight, DefaultNodeHeight); + Self.DefaultNodeHeight := lTextHeight; + end;// if + end;// if HandelAllocated +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.BeginDrag(Immediate: Boolean; Threshold: Integer); + +// Reintroduced method to allow to start OLE drag'n drop as well as VCL drag'n drop. + +begin + if FDragType = dtVCL then + begin + DoStateChange([tsVCLDragPending]); + inherited; + end + else + if (FStates * [tsOLEDragPending, tsOLEDragging]) = [] then + begin + // Drag start position has already been recorded in WMMouseDown. + if Threshold < 0 then + FDragThreshold := Mouse.DragThreshold + else + FDragThreshold := Threshold; + if Immediate then + DoDragging(FLastClickPos) + else + DoStateChange([tsOLEDragPending]); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.BeginSynch; + +// Starts the synchronous update mode (if not already active). + +begin + if not (csDestroying in ComponentState) then + begin + if FSynchUpdateCount = 0 then + begin + DoUpdating(usBeginSynch); + + // Stop all timers... + StopTimer(ChangeTimer); + StopTimer(StructureChangeTimer); + StopTimer(ExpandTimer); + StopTimer(EditTimer); + StopTimer(HeaderTimer); + StopTimer(ScrollTimer); + StopTimer(SearchTimer); + FSearchBuffer := ''; + FLastSearchNode := nil; + DoStateChange([], [tsEditPending, tsScrollPending, tsScrolling, tsIncrementalSearching]); + + // ...and trigger pending update states. + if tsStructureChangePending in FStates then + DoStructureChange(FLastStructureChangeNode, FLastStructureChangeReason); + if tsChangePending in FStates then + DoChange(FLastChangedNode); + end + else + DoUpdating(usSynch); + end; + System.Inc(FSynchUpdateCount); + DoStateChange([tsSynchMode]); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.BeginUpdate; + +begin + Assert(GetCurrentThreadId = MainThreadId, 'UI controls like ' + Classname + ' should only be manipulated through the main thread.'); + if not (csDestroying in ComponentState) then + begin + if FUpdateCount = 0 then + begin + DoUpdating(usBegin); + SetUpdateState(True); + end + else + DoUpdating(usUpdate); + end; + System.Inc(FUpdateCount); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CancelCutOrCopy; + +// Resets nodes which are marked as being cut. + +var + Run: PVirtualNode; + +begin + if ([tsCutPending, tsCopyPending] * FStates) <> [] then + begin + Run := FRoot.FirstChild; + while Assigned(Run) do + begin + if vsCutOrCopy in Run.States then + Exclude(Run.States, vsCutOrCopy); + Run := GetNextNoInit(Run); + end; + end; + DoStateChange([], [tsCutPending, tsCopyPending]); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CancelEditNode: Boolean; + +// Called by the application or the current edit link to cancel the edit action. + +begin + if HandleAllocated and ([tsEditing, tsEditPending] * FStates <> []) then + Result := DoCancelEdit + else + Result := True; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CancelOperation; + +// Called by the application to cancel a long-running operation. + +begin + if FOperationCount > 0 then + FOperationCanceled := True; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CanEdit(Node: PVirtualNode; Column: TColumnIndex): Boolean; + +// Returns True if the given node can be edited. + +begin + Result := (toEditable in FOptions.MiscOptions) and Enabled and not (toReadOnly in FOptions.MiscOptions) + and ((Column < 0) or (coEditable in FHeader.Columns[Column].Options)); + DoCanEdit(Node, Column, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CanFocus: Boolean; + +var + Form: TCustomForm; + +begin + Result := inherited CanFocus; + + if Result and not (csDesigning in ComponentState) then + begin + Form := GetParentForm(Self); + Result := (Form = nil) or (Form.Enabled and Form.Visible); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.Clear; + +begin + if (not IsEmpty and not (toReadOnly in FOptions.MiscOptions)) or (csDestroying in ComponentState) then + begin + BeginUpdate; + try + InterruptValidation; + if IsEditing then + CancelEditNode; + + if ClipboardStates * FStates <> [] then + begin + OleSetClipboard(nil); + DoStateChange([], ClipboardStates); + end; + ClearSelection; + FFocusedNode := nil; + FLastSelected := nil; + FCurrentHotNode := nil; + FDropTargetNode := nil; + FLastChangedNode := nil; + FRangeAnchor := nil; + FLastVCLDragTarget := nil; + FLastSearchNode := nil; + DeleteChildren(FRoot, True); + FOffsetX := 0; + FOffsetY := 0; + + finally + EndUpdate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ClearChecked; + +var + Node: PVirtualNode; + +begin + Node := RootNode.FirstChild; + while Assigned(Node) do + begin + if Node.CheckState <> csUncheckedNormal then + CheckState[Node] := csUncheckedNormal; + Node := GetNextNoInit(Node); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ClearSelection(); +begin + ClearSelection(True); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ClearDragManager; +begin + Pointer(FDragManager) := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ClearSelection(pFireChangeEvent: Boolean); + +var + Node: PVirtualNode; + Dummy: TDimension; + R: TRect; + Counter: Integer; + +begin + Assert(GetCurrentThreadId = MainThreadId, Self.Classname + '.ClearSelection() must only be called from UI thread.'); + if not FSelectionLocked and (FSelectionCount > 0) and not (csDestroying in ComponentState) then + begin + if (FUpdateCount = 0) and HandleAllocated and (FVisibleCount > 0) then + begin + // Iterate through nodes currently visible in the client area and invalidate them. + Node := GetNodeAt(0, 0, True, Dummy); + if Assigned(Node) then + R := GetDisplayRect(Node, NoColumn, False); + Counter := FSelectionCount; + + while Assigned(Node) do + begin + R.Bottom := R.Top + NodeHeight[Node]; + if vsSelected in Node.States then + begin + InvalidateRect(@R, False); + System.Dec(Counter); + // Only try as many nodes as are selected. + if Counter = 0 then + Break; + end; + R.Top := R.Bottom; + if R.Top > ClientHeight then + Break; + Node := GetNextVisibleNoInit(Node, True); + end; + end; + + InternalClearSelection; + if pFireChangeEvent then + Change(nil); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CopyTo(Source: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode; + ChildrenOnly: Boolean): PVirtualNode; + +// A simplified CopyTo method to allow to copy nodes to the root of another tree. + +begin + Result := CopyTo(Source, Tree.FRoot, Mode, ChildrenOnly); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CopyTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode; + ChildrenOnly: Boolean): PVirtualNode; + +// Copies Source and all its child nodes to Target. +// Mode is used to specify further where to add the new node actually (as sibling of Target or as child of Target). +// Result is the newly created node to which source has been copied if ChildrenOnly is False or just contains Target +// in the other case. +// ChildrenOnly determines whether to copy also the source node or only its child nodes. + +var + TargetTree: TBaseVirtualTree; + Stream: TMemoryStream; + +begin + Assert(TreeFromNode(Source) = Self, 'The source tree must contain the source node.'); + + Result := nil; + if (Mode <> amNoWhere) and Assigned(Source) and (Source <> FRoot) then + begin + // Assume that an empty destination means the root in this (the source) tree. + if Target = nil then + begin + TargetTree := Self; + Target := FRoot; + Mode := amAddChildFirst; + end + else + TargetTree := TreeFromNode(Target); + + if not (toReadOnly in TargetTree.TreeOptions.MiscOptions) then + begin + if Target = TargetTree.FRoot then + begin + case Mode of + amInsertBefore: + Mode := amAddChildFirst; + amInsertAfter: + Mode := amAddChildLast; + end; + end; + + Stream := TMemoryStream.Create; + try + // Write all nodes into a temprary stream depending on the ChildrenOnly flag. + if not ChildrenOnly then + WriteNode(Stream, Source) + else + begin + Source := Source.FirstChild; + while Assigned(Source) do + begin + WriteNode(Stream, Source); + Source := Source.NextSibling; + end; + end; + // Now load the serialized nodes into the target node (tree). + TargetTree.BeginUpdate; + try + Stream.Position := 0; + while Stream.Position < Stream.Size do + begin + Result := TargetTree.MakeNewNode; + InternalConnectNode(Result, Target, TargetTree, Mode); + TargetTree.InternalAddFromStream(Stream, VTTreeStreamVersion, Result); + if not DoNodeCopying(Result, Target) then + begin + TargetTree.DeleteNode(Result); + Result := nil; + end + else + DoNodeCopied(Result); + end; + if ChildrenOnly then + Result := Target; + finally + TargetTree.EndUpdate; + end; + finally + Stream.Free; + end; + + with TargetTree do + begin + InvalidateCache; + if FUpdateCount = 0 then + begin + ValidateCache; + UpdateScrollBars(True); + Invalidate; + end; + StructureChange(Source, crNodeCopied); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DeleteChildren(Node: PVirtualNode; ResetHasChildren: Boolean = False); + +// Removes all children and their children from memory without changing the vsHasChildren style by default. + +var + Run, + Mark: PVirtualNode; + LastTop, + LastLeft: TDimension; + NewSize: Integer; + ParentVisible: Boolean; + +begin + if Assigned(Node) and (Node.ChildCount > 0) and not (toReadOnly in FOptions.MiscOptions) then + begin + Assert(not (tsIterating in FStates), 'Deleting nodes during tree iteration leads to invalid pointers.'); + + // The code below uses some flags for speed improvements which may cause invalid pointers if updates of + // the tree happen. Hence switch updates off until we have finished the operation. + System.Inc(FUpdateCount); + try + InterruptValidation; + LastLeft := -FEffectiveOffsetX; + LastTop := FOffsetY; + + // Make a local copy of the visibility state of this node to speed up + // adjusting the visible nodes count. + ParentVisible := Node = FRoot; + if not ParentVisible then + ParentVisible := FullyVisible[Node] and (vsExpanded in Node.States); + + // Show that we are clearing the child list, to avoid registering structure change events. + Run := Node.LastChild; + while Assigned(Run) do + begin + if ParentVisible and IsEffectivelyVisible[Run] then + System.Dec(FVisibleCount); + + Include(Run.States, vsDeleting); + Mark := Run; + Run := Run.PrevSibling; + // Important, to avoid exchange of invalid pointers while disconnecting the node. + if Assigned(Run) then + Run.SetNextSibling(nil); + DeleteNode(Mark, False, True); + end; + if ResetHasChildren then + Exclude(Node.States, vsHasChildren); + if Node <> FRoot then + Exclude(Node.States, vsExpanded); + Node.SetChildCount(0); + if (Node = FRoot) or (vsDeleting in Node.States) then + begin + Node.TotalHeight := FDefaultNodeHeight + NodeHeight[Node]; + Node.TotalCount := 1; + end + else + begin + AdjustTotalHeight(Node, NodeHeight[Node]); + AdjustTotalCount(Node, 1); + end; + Node.SetFirstChild(nil); + Node.SetLastChild(nil); + finally + System.Dec(FUpdateCount); + end; + + InvalidateCache; + if FUpdateCount = 0 then + begin + NewSize := PackArray(FSelection, FSelectionCount); + if NewSize > -1 then + begin + FSelectionCount := NewSize; + SetLength(FSelection, FSelectionCount); + end; + + ValidateCache; + UpdateScrollBars(True); + // Invalidate entire tree if it scrolled e.g. to make the last node also the + // bottom node in the treeview. + if (LastLeft <> FOffsetX) or (LastTop <> FOffsetY) then + Invalidate + else + InvalidateToBottom(Node); + if tsChangePending in FStates then begin + DoChange(FLastChangedNode); + EnsureNodeSelected(True); + end; + end; + StructureChange(Node, crChildDeleted); + end + else if ResetHasChildren then + Exclude(Node.States, vsHasChildren); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DeleteNode(Node: PVirtualNode; Reindex: Boolean; ParentClearing: Boolean); + +var + LastTop, + LastLeft: TDimension; + LastParent: PVirtualNode; + WasInSynchMode: Boolean; + +begin + if Assigned(Node) and (Node <> FRoot) and not (toReadOnly in FOptions.MiscOptions) then + begin + Assert(not (tsIterating in FStates), 'Deleting nodes during tree iteration leads to invalid pointers.'); + + // Determine parent node for structure change notification. + LastParent := Node.Parent; + + if not ParentClearing then + begin + if LastParent = FRoot then + StructureChange(nil, crChildDeleted) + else + StructureChange(LastParent, crChildDeleted); + if Node = FNextNodeToSelect then + FNextNodeToSelect := nil; + end; + + LastLeft := -FEffectiveOffsetX; + LastTop := FOffsetY; + + if tsHint in FStates then + begin + Application.CancelHint; + DoStateChange([], [tsHint]); + end; + + if not ParentClearing then + InterruptValidation; + + DeleteChildren(Node); + + if vsSelected in Node.States then + begin + if FUpdateCount = 0 then + begin + // Go temporarily into sync mode to avoid a delayed change event for the node + // when unselecting. + WasInSynchMode := tsSynchMode in FStates; + Include(FStates, tsSynchMode); + RemoveFromSelection(Node); + //EnsureNodeSelected(); // also done in DoFreeNode() + if not WasInSynchMode then + Exclude(FStates, tsSynchMode); + InvalidateToBottom(LastParent); + end + else + InternalRemoveFromSelection(Node); + end + else + InvalidateToBottom(LastParent); + + InternalDisconnectNode(Node, False, Reindex); + DoFreeNode(Node); + + if not ParentClearing then + begin + if FUpdateCount = 0 then + DetermineHiddenChildrenFlag(LastParent) + else + Include(FStates, tsUpdateHiddenChildrenNeeded); + InvalidateCache; + if FUpdateCount = 0 then + begin + ValidateCache; + UpdateScrollBars(True); + // Invalidate entire tree if it scrolled e.g. to make the last node also the + // bottom node in the treeview. + if (LastLeft <> FOffsetX) or (LastTop <> FOffsetY) then + Invalidate; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DeleteNode(Node: PVirtualNode; pReIndex: Boolean = True); +begin + DeleteNode(Node, pReIndex, False); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DeleteNodes(const pNodes: TNodeArray); + + // Deletes all given nodes. + // Best performance is achieved if nodes are sorted by parent + +var + I: Integer; + LevelChange: Boolean; +begin + if Length(pNodes) = 0 then + exit; // Prevent range error below when empty array is passen. See issue #1288 + BeginUpdate; + try + for I := High(pNodes) downto 1 do + begin + LevelChange := pNodes[I].Parent <> pNodes[I - 1].Parent; + DeleteNode(pNodes[I], LevelChange, False); + end; + DeleteNode(pNodes[0]); + finally + EndUpdate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DeleteSelectedNodes; + +// Deletes all currently selected nodes (including their child nodes). + +var + lNodes: TNodeArray; +begin + lNodes := nil; + if (FSelectionCount > 0) and not (toReadOnly in FOptions.MiscOptions) then + begin + lNodes := GetSortedSelection(True); + DeleteNodes(lNodes); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.Dragging: Boolean; + +begin + // Check for both OLE drag'n drop as well as VCL drag'n drop. + Result := ([tsOLEDragPending, tsOLEDragging] * FStates <> []) or inherited Dragging; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.EditNode(Node: PVirtualNode; Column: TColumnIndex): Boolean; + +// Application triggered edit event for the given node. +// Returns True if the tree started editing otherwise False. + +begin + Assert(Assigned(Node), 'Node must not be nil.'); + Assert((Column > InvalidColumn) and (Column < FHeader.Columns.Count), + 'Column must be a valid column index (-1 if no header is shown).'); + + Result := tsEditing in FStates; + // If the tree is already editing then we don't disrupt this. + if not Result and not (toReadOnly in FOptions.MiscOptions) then + begin + FocusedNode := Node; + if Assigned(FFocusedNode) and (Node = FFocusedNode) and CanEdit(FFocusedNode, Column) then + begin + FEditColumn := Column; + if not (vsInitialized in Node.States) then + InitNode(Node); + DoEdit; + Result := tsEditing in FStates; + end + else + Result := False; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.EndEditNode: Boolean; + +// Called to finish a current edit action or stop the edit timer if an edit operation is pending. +// Returns True if editing was successfully ended or the control was not in edit mode +// Returns False if the control could not leave the edit mode e.g. due to an invalid value that was entered. + +begin + if [tsEditing, tsEditPending] * FStates <> [] then + Result := DoEndEdit + else + Result := True; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.EndSynch; + +begin + if FSynchUpdateCount > 0 then + System.Dec(FSynchUpdateCount); + + if not (csDestroying in ComponentState) then + begin + if FSynchUpdateCount = 0 then + begin + DoStateChange([], [tsSynchMode]); + DoUpdating(usEndSynch); + end + else + DoUpdating(usSynch); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.EndUpdate; + +var + NewSize: Integer; + +begin + if FUpdateCount = 0 then + exit; + System.Dec(FUpdateCount); + + if not (csDestroying in ComponentState) then + begin + if (FUpdateCount = 0) then + begin + if tsUpdateHiddenChildrenNeeded in FStates then + begin + DetermineHiddenChildrenFlagAllNodes; + Exclude(FStates, tsUpdateHiddenChildrenNeeded); + end; + + NewSize := PackArray(FSelection, FSelectionCount); + if NewSize > -1 then + begin + FSelectionCount := NewSize; + SetLength(FSelection, FSelectionCount); + end; + + InvalidateCache; + ValidateCache; + if HandleAllocated then + UpdateScrollBars(False); + + if tsStructureChangePending in FStates then + DoStructureChange(FLastStructureChangeNode, FLastStructureChangeReason); + try + if tsChangePending in FStates then + DoChange(FLastChangedNode); + finally + if (toAutoSort in FOptions.AutoOptions) then + SortTree(FHeader.SortColumn, FHeader.SortDirection, True); + + SetUpdateState(False); + if HandleAllocated then + Invalidate; + UpdateDesigner; + end; + NotifyAccessibleEvent(); // See issue #1174 + + DoUpdating(usEnd); + EnsureNodeSelected(False); + end + else + DoUpdating(usUpdate); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ExecuteAction(Action: TBasicAction): Boolean; + +// Some support for standard actions. + +begin + Result := inherited ExecuteAction(Action); + + if not Result then + begin + Result := Action is TEditSelectAll; + if Result then + SelectAll(False) + else + begin + Result := Action is TEditCopy; + if Result then + CopyToClipboard + else + if not (toReadOnly in FOptions.MiscOptions) then + begin + Result := Action is TEditCut; + if Result then + CutToClipboard + else + begin + Result := Action is TEditPaste; + if Result then + PasteFromClipboard + else + begin + Result := Action is TEditDelete; + if Result then + DeleteSelectedNodes; + end; + end; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.FinishCutOrCopy; + +// Deletes nodes which are marked as being cutted. + +var + Run: PVirtualNode; + +begin + if tsCutPending in FStates then + begin + Run := FRoot.FirstChild; + while Assigned(Run) do + begin + if vsCutOrCopy in Run.States then + DeleteNode(Run); + Run := GetNextNoInit(Run); + end; + DoStateChange([], [tsCutPending]); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.FlushClipboard; + +// Used to render the data which is currently on the clipboard (finishes delayed rendering). + +begin + if ClipboardStates * FStates <> [] then + begin + DoStateChange([tsClipboardFlushing]); + OleFlushClipboard; + CancelCutOrCopy; + DoStateChange([], [tsClipboardFlushing]); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.FullCollapse(Node: PVirtualNode = nil); + +// This routine collapses all expanded nodes in the subtree given by Node or the whole tree if Node is FRoot or nil. +// Only nodes which are expanded will be collapsed. This excludes uninitialized nodes but nodes marked as visible +// will still be collapsed if they are expanded. + +var + Stop: PVirtualNode; + +begin + if FRoot.TotalCount > 1 then + begin + if Node = FRoot then + Node := nil; + + DoStateChange([tsCollapsing]); + BeginUpdate; + try + Stop := Node; + Node := GetLastVisibleNoInit(Node, True); + + if Assigned(Node) then + begin + repeat + if [vsHasChildren, vsExpanded] * Node.States = [vsHasChildren, vsExpanded] then + ToggleNode(Node); + Node := GetPreviousNoInit(Node, True); + until (Node = Stop) or not Assigned(Node); + + // Collapse the start node too. + if Assigned(Stop) and ([vsHasChildren, vsExpanded] * Stop.States = [vsHasChildren, vsExpanded]) then + ToggleNode(Stop); + end; + finally + EndUpdate; + DoStateChange([], [tsCollapsing]); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.FullExpand(Node: PVirtualNode = nil); + +// This routine expands all collapsed nodes in the subtree given by Node or the whole tree if Node is FRoot or nil. +// All nodes on the way down are initialized so this procedure might take a long time. +// Since all nodes are validated, the tree cannot make use of optimatizations. Hence it is counter productive and you +// should consider avoiding its use. + +var + Stop: PVirtualNode; + +begin + if FRoot.TotalCount > 1 then + begin + DoStateChange([tsExpanding]); + StartOperation(TVTOperationKind.okExpand); + BeginUpdate; + try + if Node = nil then + begin + Node := FRoot.FirstChild; + Stop := nil; + end + else + begin + Stop := Node.NextSibling; + if Stop = nil then + begin + Stop := Node; + repeat + Stop := Stop.Parent; + until (Stop = FRoot) or Assigned(Stop.NextSibling); + if Stop = FRoot then + Stop := nil + else + Stop := Stop.NextSibling; + end; + end; + + // Initialize the start node. Others will be initialized in GetNext. + if not (vsInitialized in Node.States) then + InitNode(Node); + + repeat + if not (vsExpanded in Node.States) then + ToggleNode(Node); + Node := GetNext(Node); + until (Node = Stop) or OperationCanceled; + finally + EndOperation(TVTOperationKind.okExpand); + EndUpdate; + DoStateChange([], [tsExpanding]); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetControlsAlignment: TAlignment; + +begin + Result := FAlignment; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetDisplayRect(Node: PVirtualNode; Column: TColumnIndex; TextOnly: Boolean; + Unclipped: Boolean = False; ApplyCellContentMargin: Boolean = False): TRect; + +// Determines the client coordinates the given node covers, depending on scrolling, expand state etc. +// If the given node cannot be found (because one of its parents is collapsed or it is invisible) then an empty +// rectangle is returned. +// If TextOnly is True then only the text bounds are returned, that is, the resulting rectangle's left and right border +// are updated according to bidi mode, alignment and text width of the node. +// If Unclipped is True (which only makes sense if also TextOnly is True) then the calculated text rectangle is +// not clipped if the text does not entirely fit into the text space. This is special handling needed for hints. +// If ApplyCellContentMargin is True (which only makes sense if also TextOnly is True) then the calculated text +// rectangle respects the cell content margin. +// If Column is -1 then the entire client width is used before determining the node's width otherwise the bounds of the +// particular column are used. +// Note: Column must be a valid column and is used independent of whether the header is visible or not. + +var + Temp: PVirtualNode; + LeftOffset: TDimension; + TopOffset: TNodeHeight; + CacheIsAvailable: Boolean; + TextWidth: TDimension; + CurrentBidiMode: TBidiMode; + CurrentAlignment: TAlignment; + MaxUnclippedHeight: TDimension; + TM: TTextMetric; + ExtraVerticalMargin: TDimension; + lOffsets: TVTOffsets; +begin + Assert(Assigned(Node), 'Node must not be nil.'); + Assert(Node <> FRoot, 'Node must not be the hidden root node.'); + + if not (vsInitialized in Node.States) then + InitNode(Node); + + Result := Rect(0, 0, 0, 0); + + // Check whether the node is visible (determine indentation level btw.). + if not IsEffectivelyVisible[Node] then + Exit; + + // Here we know the node is visible. + TopOffset := 0; + CacheIsAvailable := False; + if tsUseCache in FStates then + begin + // If we can use the position cache then do a binary search to find a cached node which is as close as possible + // to the current node. Iterate then through all following and visible nodes and sum up their heights. + Temp := FindInPositionCache(Node, TopOffset); + CacheIsAvailable := Assigned(Temp); + while Assigned(Temp) and (Temp <> Node) do + begin + Inc(TopOffset, NodeHeight[Temp]); + Temp := GetNextVisibleNoInit(Temp, True); + end; + end; + if not CacheIsAvailable then + begin + // If the cache is not available then go straight through all nodes up to the root and sum up their heights. + Temp := Node; + repeat + Temp := GetPreviousVisibleNoInit(Temp, True); + if Temp = nil then + Break; + Inc(TopOffset, NodeHeight[Temp]); + until False; + end; + + Result := Rect(0, TopOffset, Max(FRangeX, ClientWidth), TopOffset + NodeHeight[Node]); + + // Limit left and right bounds to the given column (if any) and move bounds according to current scroll state. + if Column > NoColumn then + begin + FHeader.Columns.GetColumnBounds(Column, Result.Left, Result.Right); + // The right column border is not part of this cell. + Dec(Result.Right); + OffsetRect(Result, 0, FOffsetY); + end + else + OffsetRect(Result, -FEffectiveOffsetX, FOffsetY); + + // Limit left and right bounds further if only the text area is required. + if TextOnly then + begin + // If the text of a node is involved then we have to consider directionality and alignment too. + if Column <= NoColumn then + begin + CurrentBidiMode := BidiMode; + CurrentAlignment := Alignment; + end + else + begin + CurrentBidiMode := FHeader.Columns[Column].BidiMode; + CurrentAlignment := FHeader.Columns[Column].Alignment; + end; + + GetOffsets(Node, lOffsets, TVTElement.ofsLabel, Column); + LeftOffset := lOffSets[TVTElement.ofsLabel]; + // Offset contains now the distance from the left or right border of the rectangle (depending on bidi mode). + // Now consider the alignment too and calculate the final result. + if CurrentBidiMode = bdLeftToRight then + begin + Inc(Result.Left, LeftOffset); + // Left-to-right reading does not need any special adjustment of the alignment. + end + else + begin + Dec(Result.Right, LeftOffset); + + // Consider bidi mode here. In RTL context does left alignment actually mean right alignment and vice versa. + ChangeBiDiModeAlignment(CurrentAlignment); + end; + + TextWidth := DoGetNodeWidth(Node, Column); + + // Keep cell height before applying cell content margin in order to increase cell height if text does not fit + // and Unclipped it true (see below). + MaxUnclippedHeight := Result.Bottom - Result.Top; + + if ApplyCellContentMargin then + DoBeforeCellPaint(Self.Canvas, Node, Column, cpmGetContentMargin, Result, Result); + + if Unclipped then + begin + // The caller requested the text coordinates unclipped. This means they must be calculated so as would + // there be enough space, regardless of column bounds etc. + // The layout still depends on the available space too, because this determines the position + // of the unclipped text rectangle. + if Result.Right - Result.Left < TextWidth - 1 then + if CurrentBidiMode = bdLeftToRight then + CurrentAlignment := taLeftJustify + else + CurrentAlignment := taRightJustify; + + // Increase cell height (up to MaxUnclippedHeight determined above) if text does not fit. + GetTextMetrics(Self.Canvas, TM); + ExtraVerticalMargin := System.Math.Min(TM.tmHeight, MaxUnclippedHeight) - (Result.Bottom - Result.Top); + if ExtraVerticalMargin > 0 then + InflateRect(Result, 0, Divide(ExtraVerticalMargin + 1, 2)); + + case CurrentAlignment of + taCenter: + begin + Result.Left := Divide(Result.Left + Result.Right - TextWidth, 2); + Result.Right := Result.Left + TextWidth; + end; + taRightJustify: + Result.Left := Result.Right - TextWidth; + else // taLeftJustify + Result.Right := Result.Left + TextWidth - 1; + end; + end + else + // Modify rectangle only if the text fits entirely into the given room. + if Result.Right - Result.Left > TextWidth then + case CurrentAlignment of + taCenter: + begin + Result.Left := Divide(Result.Left + Result.Right - TextWidth, 2); + Result.Right := Result.Left + TextWidth; + end; + taRightJustify: + Result.Left := Result.Right - TextWidth; + else // taLeftJustify + Result.Right := Result.Left + TextWidth; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetEffectivelyFiltered(Node: PVirtualNode): Boolean; + +// Checks if a node is effectively filtered out. This depends on the nodes state and the paint options. + +begin + if Assigned(Node) then + Result := (vsFiltered in Node.States) and not (toShowFilteredNodes in FOptions.PaintOptions) + else + Result := False; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetEffectivelyVisible(Node: PVirtualNode): Boolean; + +begin + Result := (vsVisible in Node.States) and not IsEffectivelyFiltered[Node]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirst(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the first node in the tree while optionally considering toChildrenAbove. + +begin + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + if vsHasChildren in FRoot.States then + begin + Result := FRoot; + + // Child nodes are the first choice if possible. + if Assigned(Result.FirstChild) then + begin + while Assigned(Result.FirstChild) do + begin + Result := Result.FirstChild; + if not (vsInitialized in Result.States) then + InitNode(Result); + + if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then + InitChildren(Result); + end; + end + else + Result := nil; + end + else + Result := nil; + end + else + Result := FRoot.FirstChild; + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstChecked(State: TCheckState = csCheckedNormal; + ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the first node in the tree with the given check state. + +begin + Result := GetNextChecked(nil, State, ConsiderChildrenAbove); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstChild(Node: PVirtualNode): PVirtualNode; + +// Returns the first child of the given node. The result node is initialized before exit. + +begin + if (Node = nil) or (Node = FRoot) then + Result := FRoot.FirstChild + else + begin + if not (vsInitialized in Node.States) then + InitNode(Node); + if vsHasChildren in Node.States then + begin + if Node.ChildCount = 0 then + InitChildren(Node); + Result := Node.FirstChild; + end + else + Result := nil; + end; + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstChildNoInit(Node: PVirtualNode): PVirtualNode; +// Determines the first child of the given node but does not initialize it. + +begin + if (Node = nil) or (Node = FRoot) then + Result := FRoot.FirstChild + else + begin + if vsHasChildren in Node.States then + Result := Node.FirstChild + else + Result := nil; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstCutCopy(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the first node in the tree which is currently marked for a clipboard operation. +// See also GetNextCutCopy for comments on initialization. + +begin + Result := GetNextCutCopy(nil, ConsiderChildrenAbove); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstInitialized(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the first node which is already initialized. + +begin + Result := GetFirstNoInit(ConsiderChildrenAbove); + if Assigned(Result) and not (vsInitialized in Result.States) then + Result := GetNextInitialized(Result, ConsiderChildrenAbove); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstLeaf: PVirtualNode; + +// Returns the first node in the tree which has currently no children. +// The result is initialized if necessary. + +begin + Result := GetNextLeaf(nil); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstLevel(NodeLevel: Cardinal): PVirtualNode; + +// Returns the first node in the tree on a specific level. +// The result is initialized if necessary. + +begin + Result := GetFirstNoInit(True); + while Assigned(Result) and (GetNodeLevel(Result) <> NodeLevel) do + Result := GetNextNoInit(Result, True); + + if Assigned(Result) and (GetNodeLevel(Result) <> NodeLevel) then // i.e. there is no node with the desired level in the tree + Result := nil; + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstNoInit(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the first node in the tree while optionally considering toChildrenAbove. +// No initialization is performed. + +begin + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + if vsHasChildren in FRoot.States then + begin + Result := FRoot; + + // Child nodes are the first choice if possible. + if Assigned(Result.FirstChild) then + begin + while Assigned(Result.FirstChild) do + Result := Result.FirstChild; + end + else + Result := nil; + end + else + Result := nil; + end + else + Result := FRoot.FirstChild; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstSelected(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the first node in the current selection while optionally considering toChildrenAbove. + +begin + Result := GetNextSelected(nil, ConsiderChildrenAbove); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; + IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the first visible node in the tree while optionally considering toChildrenAbove. +// If necessary nodes are initialized on demand. + +begin + Result := Node; + if not Assigned(Result) then + Result := FRoot; + + if vsHasChildren in Result.States then + begin + if Result.ChildCount = 0 then + InitChildren(Result); + + // Child nodes are the first choice if possible. + if Assigned(Result.FirstChild) then + begin + Result := GetFirstChild(Result); + + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + repeat + // Search the first visible sibling. + while Assigned(Result.NextSibling) and not (vsVisible in Result.States) do + begin + Result := Result.NextSibling; + // Init node on demand as this might change the visibility. + if not (vsInitialized in Result.States) then + InitNode(Result); + end; + + // If there are no visible siblings take the parent. + if not (vsVisible in Result.States) then + begin + Result := Result.Parent; + if Result = FRoot then + Result := nil; + Break; + end + else + begin + if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then + InitChildren(Result); + if (not Assigned(Result.FirstChild)) or (not (vsExpanded in Result.States)) then + Break; + end; + + Result := Result.FirstChild; + if not (vsInitialized in Result.States) then + InitNode(Result); + until False; + end + else + begin + // If there are no children or the first child is not visible then search the sibling nodes or traverse parents. + if not (vsVisible in Result.States) then + begin + repeat + // Is there a next sibling? + if Assigned(Result.NextSibling) then + begin + Result := Result.NextSibling; + // The visible state can be removed during initialization so init the node first. + if not (vsInitialized in Result.States) then + InitNode(Result); + if vsVisible in Result.States then + Break; + end + else + begin + // No sibling anymore, so use the parent's next sibling. + if Result.Parent <> FRoot then + Result := Result.Parent + else + begin + // There are no further nodes to examine, hence there is no further visible node. + Result := nil; + Break; + end; + end; + until False; + end; + end; + end + else + Result := nil; + end + else + Result := nil; + + if Assigned(Result) and not IncludeFiltered and IsEffectivelyFiltered[Result] then + Result := GetNextVisible(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the first visible child node of Node. If necessary nodes are initialized on demand. + +begin + if Node = nil then + Node := FRoot; + Result := GetFirstChild(Node); + + if Assigned(Result) and (not (vsVisible in Result.States) or + (not IncludeFiltered and IsEffectivelyFiltered[Result])) then + Result := GetNextVisibleSibling(Result, IncludeFiltered); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the first visible child node of Node. + +begin + if Node = nil then + Node := FRoot; + Result := Node.FirstChild; + if Assigned(Result) and (not (vsVisible in Result.States) or + (not IncludeFiltered and IsEffectivelyFiltered[Result])) then + Result := GetNextVisibleSiblingNoInit(Result, IncludeFiltered); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstVisibleNoInit(Node: PVirtualNode = nil; + ConsiderChildrenAbove: Boolean = True; IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the first visible node in the tree or given subtree while optionally considering toChildrenAbove. +// No initialization is performed. + +begin + Result := Node; + if not Assigned(Result) then + Result := FRoot; + + if vsHasChildren in Result.States then + begin + // Child nodes are the first choice if possible. + if Assigned(Result.FirstChild) then + begin + Result := Result.FirstChild; + + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + repeat + // Search the first visible sibling. + while Assigned(Result.NextSibling) and not (vsVisible in Result.States) do + Result := Result.NextSibling; + + // If there a no visible siblings take the parent. + if not (vsVisible in Result.States) then + begin + Result := Result.Parent; + if Result = FRoot then + Result := nil; + Break; + end + else + if (not Assigned(Result.FirstChild)) or (not (vsExpanded in Result.States))then + Break; + + Result := Result.FirstChild; + until False; + end + else + begin + // If there are no children or the first child is not visible then search the sibling nodes or traverse parents. + if not (vsVisible in Result.States) then + begin + repeat + // Is there a next sibling? + if Assigned(Result.NextSibling) then + begin + Result := Result.NextSibling; + if vsVisible in Result.States then + Break; + end + else + begin + // No sibling anymore, so use the parent's next sibling. + if Result.Parent <> FRoot then + Result := Result.Parent + else + begin + // There are no further nodes to examine, hence there is no further visible node. + Result := nil; + Break; + end; + end; + until False; + end; + end; + end + else + Result := nil; + end + else + Result := nil; + + if Assigned(Result) and not IncludeFiltered and IsEffectivelyFiltered[Result] then + Result := GetNextVisibleNoInit(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.GetHitTestInfoAt(X, Y: TDimension; Relative: Boolean; var HitInfo: THitInfo; ShiftState: TShiftState=[]); + +// Determines the node that occupies the specified point or nil if there's none. The parameter Relative determines +// whether to consider X and Y as being client coordinates (if True) or as being absolute tree coordinates. +// HitInfo is filled with flags describing the hit further. + +var + ColLeft, + ColRight: TDimension; + NodeTop: TDimension; + InitialColumn, + NextColumn: TColumnIndex; + CurrentBidiMode: TBidiMode; + CurrentAlignment: TAlignment; + NodeRect: TRect; + +begin + HitInfo.HitNode := nil; + HitInfo.HitPositions := []; + HitInfo.HitColumn := NoColumn; + + if ShiftState=[] then + ShiftState:= KeyboardStateToShiftState(); + HitInfo.ShiftState:= ShiftState; + + // Determine if point lies in the tree's client area. + if X < 0 then + Include(HitInfo.HitPositions, hiToLeft) + else + if X > Max(FRangeX, ClientWidth) then + Include(HitInfo.HitPositions, hiToRight); + + if Y < 0 then + Include(HitInfo.HitPositions, hiAbove) + else + if Y > Max(FRangeY, ClientHeight) then + Include(HitInfo.HitPositions, hiBelow); + + // Convert position into absolute coordinate if necessary. + if Relative then + begin + if X >= Header.Columns.GetVisibleFixedWidth then + Inc(X, FEffectiveOffsetX); + Inc(Y, -FOffsetY); + end; + HitInfo.HitPoint.X := X; + HitInfo.HitPoint.Y := Y; + + // If the point is in the tree area then check the nodes. + if HitInfo.HitPositions = [] then + begin + HitInfo.HitNode := GetNodeAt(X, Y, False, NodeTop); + if HitInfo.HitNode = nil then + Include(HitInfo.HitPositions, hiNowhere) + else + begin + // At this point we need some info about the node, so it must be initialized. + if not (vsInitialized in HitInfo.HitNode.States) then + InitNode(HitInfo.HitNode); + + if FHeader.UseColumns then + begin + HitInfo.HitColumn := TVirtualTreeColumnsCracker(FHeader.Columns).GetColumnAndBounds(Point(X, Y), ColLeft, ColRight, False); + // If auto column spanning is enabled then look for the last non empty column. + if toAutoSpanColumns in FOptions.AutoOptions then + begin + InitialColumn := HitInfo.HitColumn; + // Search to the left of the hit column for empty columns. + while (HitInfo.HitColumn > NoColumn) and ColumnIsEmpty(HitInfo.HitNode, HitInfo.HitColumn) do + begin + NextColumn := FHeader.Columns.GetPreviousVisibleColumn(HitInfo.HitColumn); + if NextColumn = InvalidColumn then + Break; + HitInfo.HitColumn := NextColumn; + Dec(ColLeft, FHeader.Columns[NextColumn].Width); + end; + // Search to the right of the hit column for empty columns. + repeat + InitialColumn := FHeader.Columns.GetNextVisibleColumn(InitialColumn); + if (InitialColumn = InvalidColumn) or not ColumnIsEmpty(HitInfo.HitNode, InitialColumn) then + Break; + Inc(ColRight, FHeader.Columns[InitialColumn].Width); + until False; + end; + // Make the X position and the right border relative to the start of the column. + Dec(X, ColLeft); + Dec(ColRight, ColLeft); + end + else + begin + HitInfo.HitColumn := NoColumn; + ColRight := Max(FRangeX, ClientWidth); + end; + ColLeft := 0; + + if HitInfo.HitColumn = InvalidColumn then + Include(HitInfo.HitPositions, hiNowhere) + else + begin + // From now on X is in "column" coordinates (relative to the left column border). + HitInfo.HitPositions := [hiOnItem]; + + // Avoid getting the display rect if this is not necessary. + if toNodeHeightResize in FOptions.MiscOptions then + begin + NodeRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, False); + if Y <= (NodeRect.Top - FOffsetY + 1) then + Include(HitInfo.HitPositions, hiUpperSplitter) + else + if Y >= (NodeRect.Bottom - FOffsetY - 3) then + Include(HitInfo.HitPositions, hiLowerSplitter); + end; + + if HitInfo.HitColumn <= NoColumn then + begin + CurrentBidiMode := BidiMode; + CurrentAlignment := Alignment; + end + else + begin + CurrentBidiMode := FHeader.Columns[HitInfo.HitColumn].BidiMode; + CurrentAlignment := FHeader.Columns[HitInfo.HitColumn].Alignment; + end; + + if CurrentBidiMode = bdLeftToRight then + DetermineHitPositionLTR(HitInfo, X, ColRight, CurrentAlignment) + else + DetermineHitPositionRTL(HitInfo, X, ColRight, CurrentAlignment); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetLast(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the very last node in the tree branch given by Node and initializes the nodes all the way down including the +// result. toChildrenAbove is optionally considered. By using Node = nil the very last node in the tree is returned. + +var + Next: PVirtualNode; + +begin + Result := GetLastChild(Node); + if not ConsiderChildrenAbove or not (toChildrenAbove in FOptions.PaintOptions) then + while Assigned(Result) do + begin + // Test if there is a next last child. If not keep the node from the last run. + // Otherwise use the next last child. + Next := GetLastChild(Result); + if Next = nil then + Break; + Result := Next; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetLastInitialized(Node: PVirtualNode = nil; + ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the very last initialized child node in the tree branch given by Node. + +begin + Result := GetLastNoInit(Node, ConsiderChildrenAbove); + if Assigned(Result) and not (vsInitialized in Result.States) then + Result := GetPreviousInitialized(Result, ConsiderChildrenAbove); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetLastNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the very last node in the tree branch given by Node without initialization. + +var + Next: PVirtualNode; + +begin + Result := GetLastChildNoInit(Node); + if not ConsiderChildrenAbove or not (toChildrenAbove in FOptions.PaintOptions) then + while Assigned(Result) do + begin + // Test if there is a next last child. If not keep the node from the last run. + // Otherwise use the next last child. + Next := GetLastChildNoInit(Result); + if Next = nil then + Break; + Result := Next; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetLastChild(Node: PVirtualNode): PVirtualNode; + +// Determines the last child of the given node and initializes it if there is one. + +begin + if (Node = nil) or (Node = FRoot) then + Result := FRoot.LastChild + else + begin + if not (vsInitialized in Node.States) then + InitNode(Node); + if vsHasChildren in Node.States then + begin + if Node.ChildCount = 0 then + InitChildren(Node); + Result := Node.LastChild; + end + else + Result := nil; + end; + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetLastChildNoInit(Node: PVirtualNode): PVirtualNode; + +// Determines the last child of the given node but does not initialize it. + +begin + if (Node = nil) or (Node = FRoot) then + Result := FRoot.LastChild + else + begin + if vsHasChildren in Node.States then + Result := Node.LastChild + else + Result := nil; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetLastSelected(ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the last node in the current selection while optionally considering toChildrenAbove. + +begin + Result := GetPreviousSelected(nil, ConsiderChildrenAbove); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetLastVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; + IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the very last visible node in the tree while optionally considering toChildrenAbove. +// The nodes are intialized all the way up including the result node. + +var + Run: PVirtualNode; + +begin + Result := GetLastVisibleNoInit(Node, ConsiderChildrenAbove); + + Run := Result; + while Assigned(Run) and (Run <> Node) and (Run <> RootNode) do + begin + if not (vsInitialized in Run.States) then + InitNode(Run); + Run := Run.Parent; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetLastVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + +// Determines the last visible child of the given node and initializes it if necessary. + +begin + if (Node = nil) or (Node = FRoot) then + Result := GetLastChild(FRoot) + else + if FullyVisible[Node] and (vsExpanded in Node.States) then + Result := GetLastChild(Node) + else + Result := nil; + + if Assigned(Result) and (not (vsVisible in Result.States) or + (not IncludeFiltered and IsEffectivelyFiltered[Result])) then + Result := GetPreviousVisibleSibling(Result, IncludeFiltered); + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetLastVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + +// Determines the last visible child of the given node without initialization. + +begin + if (Node = nil) or (Node = FRoot) then + Result := GetLastChildNoInit(FRoot) + else + if FullyVisible[Node] and (vsExpanded in Node.States) then + Result := GetLastChildNoInit(Node) + else + Result := nil; + + if Assigned(Result) and (not (vsVisible in Result.States) or + (not IncludeFiltered and IsEffectivelyFiltered[Result])) then + Result := GetPreviousVisibleSiblingNoInit(Result, IncludeFiltered); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetLastVisibleNoInit(Node: PVirtualNode = nil; + ConsiderChildrenAbove: Boolean = True; IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the very last visible node in the tree while optionally considering toChildrenAbove. +// Note that the visibility of all ancestor nodes of the resulting node must not be considered. +// No initialization is performed. + + //--------------- local functions ------------------------------------------- + + function GetNodeIsVisible(ChildNode: PVirtualNode): Boolean; + begin + Result := (vsVisible in ChildNode.States) and + (IncludeFiltered or not IsEffectivelyFiltered[ChildNode]); + end; + + function GetNodeHasVisibleChildren(ChildNode: PVirtualNode): Boolean; + begin + Result := (vsHasChildren in ChildNode.States) and + (vsExpanded in ChildNode.States) and + not (vsAllChildrenHidden in ChildNode.States); + end; + + function IterateChildren(ParentNode: PVirtualNode): PVirtualNode; + var + Run: PVirtualNode; + begin + Result := nil; + + Run := GetLastChildNoInit(ParentNode); // Do not use 'GetLastVisibleChildNoInit' here (see above). + while Assigned(Run) do + begin + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + if GetNodeIsVisible(Run) then + Result := Run + else if GetNodeHasVisibleChildren(Run) then + Result := IterateChildren(Run); + end else + begin + if GetNodeHasVisibleChildren(Run) then + Result := IterateChildren(Run) + else if GetNodeIsVisible(Run) then + Result := Run; + end; + + if Assigned(Result) then + break; + + Run := GetPreviousSiblingNoInit(Run); + end; + end; + + //--------------- end local functions --------------------------------------- + +var + Run: PVirtualNode; + +begin + Result := nil; + + // First, check wether the given node and all its parents are expanded. + // If not, there can not be any visible child node. + Run := Node; + while Assigned(Run) and (Run <> RootNode) do + begin + if not (vsExpanded in Run.States) then + exit; + Run := Run.Parent; + end; + + Result := IterateChildren(Node); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetMaxColumnWidth(Column: TColumnIndex; UseSmartColumnWidth: Boolean = False): TDimension; + +// This method determines the width of the largest node in the given column. +// If UseSmartColumnWidth is True then only the visible nodes which are in view will be considered +// Note: If UseSmartColumnWidth is False then every visible node in the tree will be initialized contradicting so +// the virtual paradigm. + +var + Run, + LastNode, + NextNode: PVirtualNode; + TextLeft, + CurrentWidth: TDimension; + lOffsets: TVTOffsets; +begin + if OperationCanceled then + begin + // Behave non-destructive. + Result := FHeader.Columns[Column].Width; + Exit; + end + else + Result := 0; + + StartOperation(okGetMaxColumnWidth); + try + if Assigned(FOnBeforeGetMaxColumnWidth) then + FOnBeforeGetMaxColumnWidth(FHeader, Column, UseSmartColumnWidth); + + if UseSmartColumnWidth then // Get first visible node which is in view. + Run := GetTopNode + else + Run := GetFirstVisible(nil, True); + + // Decide where to stop. + if UseSmartColumnWidth then + LastNode := GetNextVisible(BottomNode) + else + LastNode := nil; + + if hoAutoResizeInclCaption in FHeader.Options then + Result := Result + (2 * Header.Columns[Column].Margin + Header.Columns[Column].CaptionWidth + 2); + + while Assigned(Run) and not OperationCanceled do + begin + GetOffsets(Run, lOffsets, TVTElement.ofsLabel, Column); + TextLeft := lOffsets[TVTElement.ofsLabel]; + CurrentWidth := DoGetNodeWidth(Run, Column); + Inc(CurrentWidth, DoGetNodeExtraWidth(Run, Column)); + Inc(CurrentWidth, DoGetCellContentMargin(Run, Column).X); + + // Background for fix: + // DoGetNodeWidth works correctly to return just the + // headerwidth in vsMultiline state of the node. But the + // following code was adding TextLeft unnecessarily. This + // caused a width increase each time a column splitter + // was double-clicked for the option hoDblClickResize that + // really does not apply for vsMultiline case. + // Fix: If the node is multiline, leave the current width as + // it is as returned by DoGetNodeWidth logic above. + if (Column > NoColumn) and (vsMultiline in Run.States) then + Result := CurrentWidth + else + if Result < (TextLeft + CurrentWidth) then + Result := TextLeft + CurrentWidth; + + // Get next visible node and update left node position if needed. + NextNode := GetNextVisible(Run, True); + if NextNode = LastNode then + Break; + Run := NextNode; + end; + if toShowVertGridLines in FOptions.PaintOptions then + Inc(Result); + + if Assigned(FOnAfterGetMaxColumnWidth) then + FOnAfterGetMaxColumnWidth(FHeader, Column, Result); + + finally + EndOperation(okGetMaxColumnWidth); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNext(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns next node in tree while optionally considering toChildrenAbove. The Result will be initialized if needed. + +begin + Result := Node; + if Assigned(Result) then + begin + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + // If this node has no siblings use the parent. + if not Assigned(Result.NextSibling) then + begin + Result := Result.Parent; + if Result = FRoot then + begin + Result := nil; + end; + end + else + begin + // There is at least one sibling so take it. + Result := Result.NextSibling; + + // Has this node got children? Initialize them if necessary. + if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then + InitChildren(Result); + + // Now take a look at the children. + while Assigned(Result.FirstChild) do + begin + Result := Result.FirstChild; + if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then + InitChildren(Result); + end; + end; + end + else + begin + // Has this node got children? + if vsHasChildren in Result.States then + begin + // Yes, there are child nodes. Initialize them if necessary. + if Result.ChildCount = 0 then + InitChildren(Result); + end; + + // if there is no child node try siblings + if Assigned(Result.FirstChild) then + Result := Result.FirstChild + else + begin + repeat + // Is there a next sibling? + if Assigned(Result.NextSibling) then + begin + Result := Result.NextSibling; + Break; + end + else + begin + // No sibling anymore, so use the parent's next sibling. + if Result.Parent <> FRoot then + Result := Result.Parent + else + begin + // There are no further nodes to examine, hence there is no further visible node. + Result := nil; + Break; + end; + end; + until False; + end; + end; + end; + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal; + ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +begin + if (Node = nil) or (Node = FRoot) then + Result := GetFirstNoInit(ConsiderChildrenAbove) + else + Result := GetNextNoInit(Node, ConsiderChildrenAbove); + + while Assigned(Result) and (GetCheckState(Result) <> State) do + Result := GetNextNoInit(Result, ConsiderChildrenAbove); + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextChecked(Node: PVirtualNode; ConsiderChildrenAbove: Boolean): PVirtualNode; +begin + Result := Self.GetNextChecked(Node, csCheckedNormal, ConsiderChildrenAbove); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the next node in the tree which is currently marked for a clipboard operation. Since only visible nodes can +// be marked (or they are hidden after they have been marked) it is not necessary to initialize nodes to check for +// child nodes. The result, however, is initialized if necessary. + +begin + if ClipboardStates * FStates <> [] then + begin + if (Node = nil) or (Node = FRoot) then + Result := GetFirstNoInit(ConsiderChildrenAbove) + else + Result := GetNextNoInit(Node, ConsiderChildrenAbove); + while Assigned(Result) and not (vsCutOrCopy in Result.States) do + Result := GetNextNoInit(Result, ConsiderChildrenAbove); + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); + end + else + Result := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the next node in tree which is initialized. + +begin + Result := Node; + repeat + Result := GetNextNoInit(Result, ConsiderChildrenAbove); + until (Result = nil) or (vsInitialized in Result.States); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextLeaf(Node: PVirtualNode): PVirtualNode; + +// Returns the next node in the tree which has currently no children. +// The result is initialized if necessary. + +begin + if (Node = nil) or (Node = FRoot) then + Result := FRoot.FirstChild + else + Result := GetNext(Node); + while Assigned(Result) and (vsHasChildren in Result.States) do + Result := GetNext(Result); + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode; + +// Returns the next node in the tree on a specific level. +// The result is initialized if necessary. + +var + StartNodeLevel: Cardinal; + +begin + Result := nil; + + if Assigned(Node) and (Node <> FRoot) then + begin + StartNodeLevel := GetNodeLevel(Node); + + if StartNodeLevel < NodeLevel then + begin + Result := GetNext(Node); + if Assigned(Result) and (GetNodeLevel(Result) <> NodeLevel) then + Result := GetNextLevel(Result, NodeLevel); + end + else + if StartNodeLevel = NodeLevel then + begin + Result := Node.NextSibling; + if not Assigned(Result) then // i.e. start node was a last sibling + begin + Result := Node.Parent; + if Assigned(Result) then + begin + // go to next anchestor of the start node which has a next sibling (if exists) + while Assigned(Result) and not Assigned(Result.NextSibling) do + Result := Result.Parent; + if Assigned(Result) then + Result := GetNextLevel(Result.NextSibling, NodeLevel); + end; + end; + end + else + // i.e. StartNodeLevel > NodeLevel + Result := GetNextLevel(Node.Parent, NodeLevel); + end; + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean): PVirtualNode; + +// Optimized version of GetNext performing no initialization, but optionally considering toChildrenAbove. + +begin + Result := Node; + if Assigned(Result) then + begin + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + // If this node has no siblings use the parent. + if not Assigned(Result.NextSibling) then + begin + Result := Result.Parent; + if Result = FRoot then + begin + Result := nil; + end; + end + else + begin + // There is at least one sibling so take it. + Result := Result.NextSibling; + + // Now take a look at the children. + while Assigned(Result.FirstChild) do + begin + Result := Result.FirstChild; + end; + end; + end + else + begin + // If there is no child node try siblings. + if Assigned(Result.FirstChild) then + Result := Result.FirstChild + else + begin + repeat + // Is there a next sibling? + if Assigned(Result.NextSibling) then + begin + Result := Result.NextSibling; + Break; + end + else + begin + // No sibling anymore, so use the parent's next sibling. + if Result.Parent <> FRoot then + Result := Result.Parent + else + begin + // There are no further nodes to examine, hence there is no further visible node. + Result := nil; + Break; + end; + end; + until False; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the next node in the tree which is currently selected. Since children of unitialized nodes cannot be +// in the current selection (because they simply do not exist yet) it is not necessary to initialize nodes here. +// The result however is initialized if necessary. + +begin + if FSelectionCount > 0 then + begin + if (Node = nil) or (Node = FRoot) then + Result := GetFirstNoInit(ConsiderChildrenAbove) + else + Result := GetNextNoInit(Node, ConsiderChildrenAbove); + while Assigned(Result) and not (vsSelected in Result.States) do + Result := GetNextNoInit(Result, ConsiderChildrenAbove); + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); + end + else + Result := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextSibling(Node: PVirtualNode): PVirtualNode; + +// Returns the next sibling of Node and initializes it if necessary. + +begin + Result := Node; + if Assigned(Result) then + begin + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + Result := Result.NextSibling; + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); + end; +end; + +function TBaseVirtualTree.GetNextSiblingNoInit(Node: PVirtualNode): PVirtualNode; + +// Returns the next sibling of Node. + +begin + Result := Node; + if Assigned(Result) then + begin + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + Result := Result.NextSibling; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; + +// Returns next node in tree, with regard to Node, which is visible. +// Nodes which need an initialization (including the result) are initialized. +// toChildrenAbove is optionally considered which is the default here. + +var + TopInvisibleParent: PVirtualNode; + ForceSearch: Boolean; + +begin + Result := Node; + if not Assigned(Result) then Exit; + + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + repeat + // If any ancestor is invisible, then find the last (furthest) parent node + // which is invisible to skip invisible subtrees. Otherwise we will + // likely go unnecessarily through a whole bunch of invisible nodes. + TopInvisibleParent := GetTopInvisibleParent(Result); + if Assigned(TopInvisibleParent) then + Result := TopInvisibleParent; + + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + repeat + // If there a no siblings anymore, go up one level. + if not Assigned(Result.NextSibling) then + begin + Result := Result.Parent; + if Result = FRoot then + begin + Result := nil; + Break; + end; + + if not (vsInitialized in Result.States) then + InitNode(Result); + end + else + begin + // There is at least one sibling so take it. + Result := Result.NextSibling; + if not (vsInitialized in Result.States) then + InitNode(Result); + if not (vsVisible in Result.States) then + Continue; + + // Now take a look at the children. As the children are initialized + // while toggling, we don't need to call 'InitChildren' beforehand here. + while (vsExpanded in Result.States) and Assigned(Result.FirstChild) do + begin + Result := Result.FirstChild; + if not (vsInitialized in Result.States) then + InitNode(Result); + if not (vsVisible in Result.States) then + Break; + end; + end; + + // If we found a visible node we don't need to search any longer. + // As it has already been initialized above, we don't need to call 'InitNode' here. + if vsVisible in Result.States then + Break; + until False; + end + else + begin + ForceSearch := True; + // If we found an invisible ancestor, we must not check its children. + // Remember, that TopInvisibleParent can be effectively invisible merely due to + // its own parent's expansion state despite being visible itself. + if Result <> TopInvisibleParent then + begin + if not (vsInitialized in Result.States) then + InitNode(Result); + + // Child nodes are the first choice if the current node is known to be visible. + if (vsVisible in Result.States) and (vsExpanded in Result.States) then + begin + // Initialize the node's children if necessary. + if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then + InitChildren(Result); + + if Assigned(Result.FirstChild) then + begin + Result := Result.FirstChild; + if not (vsInitialized in Result.States) then + InitNode(Result); + ForceSearch := False; + end; + end; + end; + + // If there are no children or the first child is not visible then search the sibling nodes or traverse parents. + if ForceSearch or not (vsVisible in Result.States) then + begin + repeat + // Is there a next sibling? + if Assigned(Result.NextSibling) then + begin + Result := Result.NextSibling; + if not (vsInitialized in Result.States) then + InitNode(Result); + if vsVisible in Result.States then + Break; + end + // No sibling anymore, so use the parent's next sibling. + else if Result.Parent <> FRoot then + Result := Result.Parent + else + begin + // There are no further nodes to examine, hence there is no further visible node. + Result := nil; + Break; + end; + until False; + end; + end; + until not Assigned(Result) or IsEffectivelyVisible[Result]; + + Assert(Result <> Node, 'Node cannot be its own visible successor.'); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; + +// Returns the next node in tree, with regard to Node, which is visible. +// No initialization is done. +// toChildrenAbove is optionally considered which is the default here. + +var + TopInvisibleParent: PVirtualNode; + ForceSearch: Boolean; + +begin + Result := Node; + if not Assigned(Result) then Exit; + + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + repeat + // If any ancestor is invisible, then find the last (furthest) parent node + // which is invisible to skip invisible subtrees. Otherwise we will + // likely go unnecessarily through a whole bunch of invisible nodes. + TopInvisibleParent := GetTopInvisibleParent(Result); + if Assigned(TopInvisibleParent) then + Result := TopInvisibleParent; + + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + repeat + // If there are no siblings anymore, go up one level. + if not Assigned(Result.NextSibling) then + begin + Result := Result.Parent; + if Result = FRoot then + begin + Result := nil; + Break; + end; + end + else + begin + // There is at least one sibling so take it. + Result := Result.NextSibling; + if not (vsVisible in Result.States) then + Continue; + + // Now take a look at the children. + while (vsExpanded in Result.States) and Assigned(Result.FirstChild) do + begin + Result := Result.FirstChild; + if not (vsVisible in Result.States) then + Break; + end; + end; + + // If we found a visible node we don't need to search any longer. + if vsVisible in Result.States then + Break; + until False; + end + else + begin + // Child nodes are the first choice if the current node is known to be visible. + // Remember, that TopInvisibleParent can be effectively invisible merely due to + // its own parent's expansion state despite being visible itself. + if (vsVisible in Result.States) and (vsExpanded in Result.States) and + (Result <> TopInvisibleParent) and Assigned(Result.FirstChild) then + begin + Result := Result.FirstChild; + ForceSearch := False; + end else + ForceSearch := True; + + // If there are no children or the first child is not visible then search the sibling nodes or traverse parents. + if ForceSearch or not (vsVisible in Result.States) then + begin + repeat + // Is there a next sibling? + if Assigned(Result.NextSibling) then + begin + Result := Result.NextSibling; + if vsVisible in Result.States then + Break; + end + // No sibling anymore, so use the parent's next sibling. + else if Result.Parent <> FRoot then + Result := Result.Parent + else + begin + // There are no further nodes to examine, hence there is no further visible node. + Result := nil; + Break; + end; + until False; + end; + end; + until not Assigned(Result) or IsEffectivelyVisible[Result]; + + Assert(Result <> Node, 'Node cannot be its own visible successor.'); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the next visible sibling after Node. Initialization is done implicitly. + +begin + Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); + + Result := Node; + repeat + Result := GetNextSibling(Result); + until not Assigned(Result) or ((vsVisible in Result.States) and + (IncludeFiltered or not IsEffectivelyFiltered[Result])); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNextVisibleSiblingNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the next visible sibling after Node. + +begin + Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); + + Result := Node; + repeat + Result := Result.NextSibling; + until not Assigned(Result) or ((vsVisible in Result.States) and + (IncludeFiltered or not IsEffectivelyFiltered[Result])); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNodeAt(X, Y: TDimension): PVirtualNode; + +// Overloaded variant of GetNodeAt to easy life of application developers which do not need to have the exact +// top position returned and always use client coordinates. + +var + Dummy: TDimension; + +begin + Result := GetNodeAt(X, Y, True, Dummy); +end; + +function TBaseVirtualTree.GetNodeAt(const P: TPoint): PVirtualNode; +begin + Result := GetNodeAt(P.X, P.Y); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNodeAt(X, Y: TDimension; Relative: Boolean; var NodeTop: TDimension): PVirtualNode; + +// This method returns the node that occupies the specified point, or nil if there's none. +// If Releative is True then X and Y are given in client coordinates otherwise they are considered as being +// absolute values into the virtual tree image (regardless of the current offsets in the tree window). +// NodeTop gets the absolute or relative top position of the node returned or is untouched if no node +// could be found. + +var + AbsolutePos, + CurrentPos: TNodeHeight; + +begin + if Y < 0 then + Y := 0; + + AbsolutePos := Y; + if Relative then + Inc(AbsolutePos, -FOffsetY); + + // CurrentPos tracks a running term of the current position to test for. + // It corresponds always to the top position of the currently considered node. + CurrentPos := 0; + + // If the cache is available then use it. + if tsUseCache in FStates then + Result := FindInPositionCache(AbsolutePos, CurrentPos) + else + Result := GetFirstVisibleNoInit(nil, True); + + // Determine node, of which position and height corresponds to the scroll position most closely. + while Assigned(Result) and (Result <> FRoot) do + begin + if AbsolutePos < (CurrentPos + NodeHeight[Result]) then + Break; + Inc(CurrentPos, NodeHeight[Result]); + Result := GetNextVisibleNoInit(Result, True); + end; + + if Result = FRoot then + Result := nil; + + // Since the given vertical position is likely not the same as the top position + // of the found node this top position is returned. + if Assigned(Result) then + begin + NodeTop := CurrentPos; + if Relative then + Inc(NodeTop, FOffsetY); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + + +function TBaseVirtualTree.GetNodeData(Node: PVirtualNode): Pointer; + +// Returns the address of the user defined data area in the node. + +begin + Assert((FNodeDataSize > 0) or not Assigned(Node), 'NodeDataSize not initialized.'); + if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) then + Result := nil + else + begin + Result := Node.GetData(); + Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323 + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNodeData(pNode: PVirtualNode): T; + +// Returns the associated data converted to the class given in the generic part of the function. + +var + P: Pointer; +begin + P := Self.GetNodeData(pNode); + if Assigned(P) then + Exit(T(P^)) + else + Exit(Default(T)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetInterfaceFromNodeData(pNode: PVirtualNode): T; +begin + if Assigned(pNode) then + Result := T(Self.GetNodeData(pNode)^) + else + Result := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNodeDataAt(pXCoord, pYCoord: Integer): T; + +// Returns the associated data at the specified coordinates converted to the type given in the generic part of the function. + +var + lNode: PVirtualNode; +begin + lNode := GetNodeAt(pXCoord, pYCoord); + Result := Self.GetNodeData(lNode); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetFirstSelectedNodeData(): T; + +// Returns of the first selected node associated data converted to the type given in the generic part of the function. + +begin + Result := Self.GetNodeData(GetFirstSelected()); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetNodeLevel(Node: PVirtualNode): Cardinal; + +// returns the level of the given node + +var + Run: PVirtualNode; + +begin + Result := 0; + if Assigned(Node) and (Node <> FRoot) then + begin + Run := Node.Parent; + while Run <> FRoot do + begin + Run := Run.Parent; + System.Inc(Result); + end; + end; +end; + + +//---------------------------------------------------------------------------------------------------------------------- +// Function introduced to avoid spaghetti code to fix setting of FLastSelectionLevel +// at various places that now needs to avoid setting it for a disabled node +function TBaseVirtualTree.GetNodeLevelForSelectConstraint(Node: PVirtualNode): integer; +begin + if Assigned(Node) and not (vsDisabled in Node.States) then + result := GetNodeLevel(Node) + else + result := -1; +end; + + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPrevious(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns previous node in tree. If ConsiderChildrenAbove is True the function considers +// whether toChildrenAbove is currently set, otherwise the result will always be the previous +// node in top-down order regardless of the current PaintOptions. +// The Result will be initialized if needed. + +var + Run: PVirtualNode; + +begin + Result := Node; + if Assigned(Result) then + begin + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + // Has this node got children? Initialize them if necessary. + if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then + InitChildren(Result); + + // If there is a last child, take it; if not try the previous sibling. + if Assigned(Result.LastChild) then + Result := Result.LastChild + else + if Assigned(Result.PrevSibling) then + Result := Result.PrevSibling + else + begin + // If neither a last child nor a previous sibling exist, go the tree upwards and + // look, wether one of the parent nodes have a previous sibling. If not the result + // will ne nil. + repeat + Result := Result.Parent; + Run := nil; + if Result <> FRoot then + Run := Result.PrevSibling + else + Result := nil; + until Assigned(Run) or (Result = nil); + + if Assigned(Run) then + Result := Run; + end; + end + else + begin + // Is there a previous sibling? + if Assigned(Node.PrevSibling) then + begin + // Go down and find the last child node. + Result := GetLast(Node.PrevSibling); + if Result = nil then + Result := Node.PrevSibling; + end + else + // no previous sibling so the parent of the node is the previous visible node + if Node.Parent <> FRoot then + Result := Node.Parent + else + Result := nil; + end; + end; + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal; + ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +begin + if (Node = nil) or (Node = FRoot) then + Result := GetLastNoInit(nil, ConsiderChildrenAbove) + else + Result := GetPreviousNoInit(Node, ConsiderChildrenAbove); + + while Assigned(Result) and (GetCheckState(Result) <> State) do + Result := GetPreviousNoInit(Result, ConsiderChildrenAbove); + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the previous node in the tree which is currently marked for a clipboard operation. Since only visible nodes can +// be marked (or they are hidden after they have been marked) it is not necessary to initialize nodes to check for +// child nodes. The result, however, is initialized if necessary. + +begin + if ClipboardStates * FStates <> [] then + begin + if (Node = nil) or (Node = FRoot) then + Result := GetLastNoInit(nil, ConsiderChildrenAbove) + else + Result := GetPreviousNoInit(Node, ConsiderChildrenAbove); + while Assigned(Result) and not (vsCutOrCopy in Result.States) do + Result := GetPreviousNoInit(Result, ConsiderChildrenAbove); + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); + end + else + Result := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the previous node in tree which is initialized. + +begin + Result := Node; + repeat + Result := GetPreviousNoInit(Result, ConsiderChildrenAbove); + until (Result = nil) or (vsInitialized in Result.States); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousLeaf(Node: PVirtualNode): PVirtualNode; + +// Returns the previous node in the tree which has currently no children. +// The result is initialized if necessary. + +begin + if (Node = nil) or (Node = FRoot) then + Result := FRoot.LastChild + else + Result := GetPrevious(Node); + while Assigned(Result) and (vsHasChildren in Result.States) do + Result := GetPrevious(Result); + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode; + +// Returns the previous node in the tree on a specific level. +// The result is initialized if necessary. + +var + StartNodeLevel: Cardinal; + Run: PVirtualNode; + +begin + Result := nil; + + if Assigned(Node) and (Node <> FRoot) then + begin + StartNodeLevel := GetNodeLevel(Node); + + if StartNodeLevel < NodeLevel then + begin + Result := Node.PrevSibling; + if Assigned(Result) then + begin + // go to last descendant of previous sibling with desired node level (if exists) + Run := Result; + while Assigned(Run) and (GetNodeLevel(Run) < NodeLevel) do + begin + Result := Run; + Run := GetLastChild(Run); + end; + if Assigned(Run) and (GetNodeLevel(Run) = NodeLevel) then + Result := Run + else + begin + if Assigned(Result.PrevSibling) then + Result := GetPreviousLevel(Result, NodeLevel) + else + if Assigned(Result) and (Result.Parent <> FRoot) then + Result := GetPreviousLevel(Result.Parent, NodeLevel) + else + Result := nil; + end; + end + else + Result := GetPreviousLevel(Node.Parent, NodeLevel); + end + else + if StartNodeLevel = NodeLevel then + begin + Result := Node.PrevSibling; + if not Assigned(Result) then // i.e. start node was a first sibling + begin + Result := Node.Parent; + if Assigned(Result) then + Result := GetPreviousLevel(Result, NodeLevel); + end; + end + else // i.e. StartNodeLevel > NodeLevel + Result := GetPreviousLevel(Node.Parent, NodeLevel); + end; + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns previous node in tree, optionally considering toChildrenAbove. No initialization is performed. + +var + Run: PVirtualNode; + +begin + Result := Node; + if Assigned(Result) then + begin + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + // If there is a last child, take it; if not try the previous sibling. + if Assigned(Result.LastChild) then + Result := Result.LastChild + else + if Assigned(Result.PrevSibling) then + Result := Result.PrevSibling + else + begin + // If neither a last child nor a previous sibling exist, go the tree upwards and + // look, wether one of the parent nodes have a previous sibling. If not the result + // will ne nil. + repeat + Result := Result.Parent; + Run := nil; + if Result <> FRoot then + Run := Result.PrevSibling + else + Result := nil; + until Assigned(Run) or (Result = nil); + + if Assigned(Run) then + Result := Run; + end; + end + else + begin + // Is there a previous sibling? + if Assigned(Node.PrevSibling) then + begin + // Go down and find the last child node. + Result := GetLastNoInit(Node.PrevSibling); + if Result = nil then + Result := Node.PrevSibling; + end + else + // No previous sibling so the parent of the node is the previous node. + if Node.Parent <> FRoot then + Result := Node.Parent + else + Result := nil; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; + +// Returns the previous node in the tree which is currently selected. Since children of unitialized nodes cannot be +// in the current selection (because they simply do not exist yet) it is not necessary to initialize nodes here. +// The result however is initialized if necessary. + +begin + if FSelectionCount > 0 then + begin + if (Node = nil) or (Node = FRoot) then + Result := GetLastNoInit(nil, ConsiderChildrenAbove) + else + Result := GetPreviousNoInit(Node, ConsiderChildrenAbove); + while Assigned(Result) and not (vsSelected in Result.States) do + Result := GetPreviousNoInit(Result, ConsiderChildrenAbove); + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); + end + else + Result := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousSibling(Node: PVirtualNode): PVirtualNode; + +// Returns the previous sibling of Node and initializes it if necessary. + +begin + Result := Node; + if Assigned(Result) then + begin + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + Result := Result.PrevSibling; + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); + end; +end; + +function TBaseVirtualTree.GetPreviousSiblingNoInit(Node: PVirtualNode): PVirtualNode; + +// Returns the previous sibling of Node + +begin + Result := Node; + if Assigned(Result) then + begin + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + Result := Result.PrevSibling; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; + +// Returns the previous node in tree, with regard to Node, which is visible. +// Nodes which need an initialization (including the result) are initialized. +// toChildrenAbove is optionally considered which is the default here. + +var + TopInvisibleParent: PVirtualNode; + ForceSearch: Boolean; + +begin + Result := Node; + if not Assigned(Result) then Exit; + + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + repeat + // If any ancestor is invisible, then find the last (furthest) parent node + // which is invisible to skip invisible subtrees. Otherwise we will + // likely go unnecessarily through a whole bunch of invisible nodes. + TopInvisibleParent := GetTopInvisibleParent(Result); + if Assigned(TopInvisibleParent) then + Result := TopInvisibleParent; + + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + ForceSearch := True; + // If we found an invisible ancestor, we must not check its children. + // Remember, that TopInvisibleParent can be effectively invisible merely due to + // its own parent's expansion state despite being visible itself. + if Result <> TopInvisibleParent then + begin + if not (vsInitialized in Result.States) then + InitNode(Result); + + if (vsVisible in Result.States) and (vsExpanded in Result.States) then + begin + // Initialiue the node's children if necessary. + if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then + InitChildren(Result); + + // Child nodes are the first choice if the current node is known to be visible. + if Assigned(Result.LastChild) then + begin + Result := Result.LastChild; + if not (vsInitialized in Result.States) then + InitNode(Result); + ForceSearch := False; + end; + end; + end; + + if ForceSearch or not (vsVisible in Result.States) then + begin + repeat + // Is there a previous sibling? + if Assigned(Result.PrevSibling) then + begin + Result := Result.PrevSibling; + if not (vsInitialized in Result.States) then + InitNode(Result); + if vsVisible in Result.States then + Break; + end + // No sibling anymore, so use the parent's previous sibling. + else if Result.Parent <> FRoot then + Result := Result.Parent + // There are no further nodes to examine, hence there is no further visible node. + else + begin + Result := nil; + Break; + end; + until False; + end; + end + else + begin + repeat + // If there are no sibling anymore, go up one level. + if not Assigned(Result.PrevSibling) then + begin + Result := Result.Parent; + if Result = FRoot then + begin + Result := nil; + Break; + end; + if not (vsInitialized in Result.States) then + InitNode(Result); + end else + begin + Result := Result.PrevSibling; + if not (vsInitialized in Result.States) then + InitNode(Result); + if not (vsVisible in Result.States) then + Continue; + + // Now take a look at the children. As the children are initialized + // while toggling, we don't need to call 'InitChildren' beforehand here. + while (vsExpanded in Result.States) and Assigned(Result.LastChild) do + begin + Result := Result.LastChild; + if not (vsInitialized in Result.States) then + InitNode(Result); + if not (vsVisible in Result.States) then + Break; + end; + end; + + // If we found a visible node we don't need to search any longer. + if vsVisible in Result.States then + Break; + until False; + end; + + if Assigned(Result) and not (vsInitialized in Result.States) then + InitNode(Result); + until not Assigned(Result) or IsEffectivelyVisible[Result]; + + Assert(Result <> Node, 'Node cannot be its own visible predecessor.'); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousVisibleNoInit(Node: PVirtualNode; + ConsiderChildrenAbove: Boolean = True): PVirtualNode; + +// Returns the previous node in tree, with regard to Node, which is visible. +// No initialization is done. +// toChildrenAbove is optionally considered which is the default here. + +var + TopInvisibleParent: PVirtualNode; + ForceSearch: Boolean; + +begin + Result := Node; + if not Assigned(Result) then Exit; + + Assert(Result <> FRoot, 'Node must not be the hidden root node.'); + + repeat + // If any ancestor is invisible, then find the last (furthest) parent node + // which is invisible to skip invisible subtrees. Otherwise we will + // likely go unnecessarily through a whole bunch of invisible nodes. + TopInvisibleParent := GetTopInvisibleParent(Result); + if Assigned(TopInvisibleParent) then + Result := TopInvisibleParent; + + if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then + begin + // Child nodes are the first choice if the current node is known to be visible. + // Remember, that TopInvisibleParent can be effectively invisible merely due to + // its own parent's expansion state despite being visible itself. + if (vsVisible in Result.States) and (vsExpanded in Result.States) and + (Result <> TopInvisibleParent) and Assigned(Result.LastChild) then + begin + Result := Result.LastChild; + ForceSearch := False; + end else + ForceSearch := True; + + if ForceSearch or not (vsVisible in Result.States) then + begin + repeat + // Is there a previous sibling? + if Assigned(Result.PrevSibling) then + begin + Result := Result.PrevSibling; + if vsVisible in Result.States then + Break; + end + // No sibling anymore, so use the parent's previous sibling. + else if Result.Parent <> FRoot then + Result := Result.Parent + // There are no further nodes to examine, hence there is no further visible node. + else + begin + Result := nil; + Break; + end; + until False; + end; + end + else + begin + repeat + // If there are no siblings anymore, go up one level. + if not Assigned(Result.PrevSibling) then + begin + Result := Result.Parent; + if Result = FRoot then + begin + Result := nil; + Break; + end; + end + else + begin + // There is at least one sibling so take it. + Result := Result.PrevSibling; + if not (vsVisible in Result.States) then + Continue; + + // Now take a look at the children. + while (vsExpanded in Result.States) and Assigned(Result.LastChild) do + begin + Result := Result.LastChild; + if not (vsVisible in Result.States) then + Break; + end; + end; + + // If we found a visible node we don't need to search any longer. + if vsVisible in Result.States then + Break; + until False; + end; + until not Assigned(Result) or IsEffectivelyVisible[Result]; + + Assert(Result <> Node, 'Node cannot be its own visible predecessor.'); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the previous visible sibling before Node. Initialization is done implicitly. + +begin + Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); + + Result := Node; + repeat + Result := GetPreviousSibling(Result); + until not Assigned(Result) or ((vsVisible in Result.States) and + (IncludeFiltered or not IsEffectivelyFiltered[Result])); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetPreviousVisibleSiblingNoInit(Node: PVirtualNode; + IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the previous visible sibling before Node. + +begin + Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); + + Result := Node; + repeat + Result := Result.PrevSibling; + until not Assigned(Result) or ((vsVisible in Result.States) and + (IncludeFiltered or not IsEffectivelyFiltered[Result])); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.Nodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; + +// Enumeration for all nodes + +begin + Result.FMode := vneAll; + Result.FTree := Self; + Result.FConsiderChildrenAbove := ConsiderChildrenAbove; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CheckedNodes(State: TCheckState; ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; + +// Enumeration for all checked nodes + +begin + Result.FMode := vneChecked; + Result.FTree := Self; + Result.FState := State; + Result.FConsiderChildrenAbove := ConsiderChildrenAbove; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ChildNodes(Node: PVirtualNode): TVTVirtualNodeEnumeration; + +// Enumeration for child nodes + +begin + Result.FMode := vneChild; + Result.FTree := Self; + Result.FNode := Node; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.CutCopyNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; + +// Enumeration for cut copy node + +begin + Result.FMode := vneCutCopy; + Result.FTree := Self; + Result.FConsiderChildrenAbove := ConsiderChildrenAbove; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.CutToClipboard; +begin + if (toReadOnly in TreeOptions.MiscOptions) then + exit; + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.InitializedNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; + +// Enumeration for initialized nodes + +begin + Result.FMode := vneInitialized; + Result.FTree := Self; + Result.FConsiderChildrenAbove := ConsiderChildrenAbove; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.LeafNodes: TVTVirtualNodeEnumeration; + +// Enumeration for leaf nodes + +begin + Result.FMode := vneLeaf; + Result.FTree := Self; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.LevelNodes(NodeLevel: Cardinal): TVTVirtualNodeEnumeration; + +// Enumeration for level nodes + +begin + Result.FMode := vneLevel; + Result.FTree := Self; + Result.FNodeLevel := NodeLevel; +end; + +function TBaseVirtualTree.LineWidth: TDimension; +// Returns the width in pixels that should be used to draw grid lines, see issue #1203 +begin + // Always use line width of 1 for older Delphi versions. + {$if CompilerVersion < 31} + Exit(1); + {$else} + if FCurrentPPI < 200 then + Exit(1) // Always use 1 pixel is scaled <=200% + else + Exit(MulDiv(1, Self.FCurrentPPI, 132)); // Use 132 dpi instead of the typical 96 so that line width increase slightly slower than the actual scaling, so we have a 3px line at 400% + {$ifend} +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.NoInitNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; + +// Enumeration for no init nodes +begin + Result.FMode := vneNoInit; + Result.FTree := Self; + Result.FConsiderChildrenAbove := ConsiderChildrenAbove; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.SelectedNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; + +// Enumeration for selected nodes + +begin + Result.FMode := vneSelected; + Result.FTree := Self; + Result.FConsiderChildrenAbove := ConsiderChildrenAbove; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.VisibleNodes(Node: PVirtualNode; ConsiderChildrenAbove: Boolean; + IncludeFiltered: Boolean): TVTVirtualNodeEnumeration; + +// Enumeration for visible nodes + +begin + Result.FMode := vneVisible; + Result.FTree := Self; + Result.FNode := Node; + Result.FConsiderChildrenAbove := ConsiderChildrenAbove; + Result.FIncludeFiltered := IncludeFiltered; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.VisibleChildNodes(Node: PVirtualNode; IncludeFiltered: Boolean): TVTVirtualNodeEnumeration; + +// Enumeration for visible child nodes + +begin + Result.FMode := vneVisibleChild; + Result.FTree := Self; + Result.FNode := Node; + Result.FIncludeFiltered := IncludeFiltered; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.VisibleChildNoInitNodes(Node: PVirtualNode; IncludeFiltered: Boolean): TVTVirtualNodeEnumeration; + +// Enumeration for visible child no init nodes + +begin + Result.FMode := vneVisibleNoInitChild; + Result.FTree := Self; + Result.FNode := Node; + Result.FIncludeFiltered := IncludeFiltered; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.VisibleNoInitNodes(Node: PVirtualNode; ConsiderChildrenAbove: Boolean; + IncludeFiltered: Boolean): TVTVirtualNodeEnumeration; + +// Enumeration for visible no init nodes + +begin + Result.FMode := vneVisibleNoInit; + Result.FTree := Self; + Result.FNode := Node; + Result.FConsiderChildrenAbove := ConsiderChildrenAbove; + Result.FIncludeFiltered := IncludeFiltered; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetSortedCutCopySet(Resolve: Boolean): TNodeArray; + +// Same as GetSortedSelection but with nodes marked as being part in the current cut/copy set (e.g. for clipboard). + +var + Run: PVirtualNode; + Counter: Cardinal; + + //--------------- local function -------------------------------------------- + + procedure IncludeThisNode(Node: PVirtualNode); + + // adds the given node to the result + + var + Len: Cardinal; + + begin + Len := Length(Result); + if Counter = Len then + begin + if Len < 100 then + Len := 100 + else + Len := Len + Len div 10; + SetLength(Result, Len); + end; + Result[Counter] := Node; + System.Inc(Counter); + end; + + //--------------- end local function ---------------------------------------- + +begin + Run := FRoot.FirstChild; + Counter := 0; + if Resolve then + begin + // Resolving is actually easy: just find the first cutted node in logical order + // and then never go deeper in level than this node as long as there's a sibling node. + // Restart the search for a cutted node (at any level) if there are no further siblings. + while Assigned(Run) do + begin + if vsCutOrCopy in Run.States then + begin + IncludeThisNode(Run); + if Assigned(Run.NextSibling) then + Run := Run.NextSibling + else + begin + // If there are no further siblings then go up one or more levels until a node is + // found or all nodes have been processed. Although we consider here only initialized + // nodes we don't need to make any special checks as only initialized nodes can also be selected. + repeat + Run := Run.Parent; + until (Run = FRoot) or Assigned(Run.NextSibling); + if Run = FRoot then + Break + else + Run := Run.NextSibling; + end; + end + else + Run := GetNextNoInit(Run); + end; + end + else + while Assigned(Run) do + begin + if vsCutOrCopy in Run.States then + IncludeThisNode(Run); + Run := GetNextNoInit(Run); + end; + + // set the resulting array to its real length + SetLength(Result, Counter); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetSortedSelection(Resolve: Boolean): TNodeArray; + +// Returns a list of selected nodes sorted in logical order, that is, as they appear in the tree. +// If Resolve is True then nodes which are children of other selected nodes are not put into the new array. +// This feature is in particuar important when doing drag'n drop as in this case all selected node plus their children +// need to be considered. A selected node which is child (grand child etc.) of another selected node is then +// automatically included and doesn't need to be explicitely mentioned in the returned selection array. +// +// Note: The caller is responsible for freeing the array. Allocation is done here. Usually, though, freeing the array +// doesn't need additional attention as it is automatically freed by Delphi when it gets out of scope. + +var + Run: PVirtualNode; + Counter: Cardinal; + +begin + SetLength(Result, FSelectionCount); + if FSelectionCount > 0 then + begin + Run := FRoot.FirstChild; + Counter := 0; + if Resolve then + begin + // Resolving is actually easy: just find the first selected node in logical order + // and then never go deeper in level than this node as long as there's a sibling node. + // Restart the search for a selected node (at any level) if there are no further siblings. + while Assigned(Run) do + begin + if vsSelected in Run.States then + begin + Result[Counter] := Run; + System.Inc(Counter); + if Assigned(Run.NextSibling) then + Run := Run.NextSibling + else + begin + // If there are no further siblings then go up one or more levels until a node is + // found or all nodes have been processed. Although we consider here only initialized + // nodes we don't need to make any special checks as only initialized nodes can also be selected. + repeat + Run := Run.Parent; + until (Run = FRoot) or Assigned(Run.NextSibling); + if Run = FRoot then + Break + else + Run := Run.NextSibling; + end; + end + else + Run := GetNextNoInit(Run); + end; + end + else + while Assigned(Run) do + begin + if vsSelected in Run.States then + begin + Result[Counter] := Run; + System.Inc(Counter); + end; + Run := GetNextNoInit(Run); + end; + + // Since we may have skipped some nodes the result array is likely to be smaller than the + // selection array, hence shorten the result to true length. + if Integer(Counter) < Length(Result) then + SetLength(Result, Counter); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect; + var Text: string); + +// Generic base method for editors, hint windows etc. to get some info about a node. + +begin + R := Rect(0, 0, 0, 0); + Text := ''; + if Assigned(Font) then // 1 EConvertError due to Font being nil seen here in 01/2019, See issue #878 + AFont.Assign(Font); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetTreeRect: TRect; + +// Returns the true size of the tree in pixels. This size is at least ClientHeight x ClientWidth and depends on +// the expand state, header size etc. +// Note: if no columns are used then the width of the tree is determined by the largest node which is currently in the +// client area. This might however not be the largest node in the entire tree. + +begin + Result := Rect(0, 0, Max(FRangeX, ClientWidth), Max(FRangeY, ClientHeight)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetVisibleParent(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; + +// Returns the first (nearest) parent node of Node which is visible. +// This method is one of the seldom cases where the hidden root node could be returned. + +begin + Assert(Assigned(Node), 'Node must not be nil.'); + Assert(Node <> FRoot, 'Node must not be the hidden root node.'); + + Result := Node.Parent; + while (Result <> FRoot) and (not FullyVisible[Result] or (not IncludeFiltered and IsEffectivelyFiltered[Result])) do + Result := Result.Parent; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetTopInvisibleParent(Node: PVirtualNode): PVirtualNode; + +// Returns the last (furthest) parent node of Node which is invisible. + +var + Run: PVirtualNode; + +begin + Assert(Assigned(Node), 'Node must not be nil.'); + Assert(Node <> FRoot, 'Node must not be the hidden root node.'); + + Result := nil; + + Run := Node.Parent; + while (Run <> FRoot) do + begin + if not ( (vsVisible in Run.States) and (vsExpanded in Run.Parent.States) ) then + Result := Run; + Run := Run.Parent; + end; + +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.HasAsParent(Node, PotentialParent: PVirtualNode): Boolean; + +// Determines whether Node has got PotentialParent as one of its parents. + +var + Run: PVirtualNode; + +begin + Result := Assigned(Node) and Assigned(PotentialParent) and (Node <> PotentialParent); + if Result then + begin + Run := Node; + while (Run <> FRoot) and (Run <> PotentialParent) do + Run := Run.Parent; + Result := Run = PotentialParent; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.InsertNode(Node: PVirtualNode; Mode: TVTNodeAttachMode; UserData: Pointer = nil): PVirtualNode; + +// Adds a new node relative to Node. The final position is determined by Mode. +// UserData can be used to set the first SizeOf(Pointer) bytes of the user data area to an initial value which can be used +// in OnInitNode and will also cause to trigger the OnFreeNode event (if <> nil) even if the node is not yet +// "officially" initialized. +// InsertNode is a compatibility method and will implicitly validate the given node if the new node +// is to be added as child node. This is however against the virtual paradigm and hence I dissuade from its usage. + +begin + if Mode <> amNoWhere then + begin + CancelEditNode; + + if Node = nil then + Node := FRoot; + // we need a new node... + Result := MakeNewNode; + // avoid erronous attach modes + if Node = FRoot then + begin + case Mode of + amInsertBefore: + Mode := amAddChildFirst; + amInsertAfter: + Mode := amAddChildLast; + end; + end; + + // Validate given node in case the new node becomes its child. + if (Mode in [amAddChildFirst, amAddChildLast]) and not (vsInitialized in Node.States) then + InitNode(Node); + InternalConnectNode(Result, Node, Self, Mode); + + // Check if there is initial user data and there is also enough user data space allocated. + if Assigned(UserData) then + SetNodeData(Result, UserData); + + if FUpdateCount = 0 then + begin + case Mode of + amInsertBefore, + amInsertAfter: + begin + // Here no initialization is necessary because *if* a node has already got children then it + // must also be initialized. + // Note: Node can never be FRoot at this point. + StructureChange(Result, crNodeAdded); + // If auto sort is enabled then sort the node or its parent (depending on the insert mode). + if (toAutoSort in FOptions.AutoOptions) and (FHeader.SortColumn > InvalidColumn) then + Sort(Node.Parent, FHeader.SortColumn, FHeader.SortDirection, True); + InvalidateToBottom(Result) + end; + amAddChildFirst, + amAddChildLast: + begin + StructureChange(Node, crChildAdded); + // If auto sort is enabled then sort the node or its parent (depending on the insert mode). + if (toAutoSort in FOptions.AutoOptions) and (FHeader.SortColumn > InvalidColumn) then + Sort(Node, FHeader.SortColumn, FHeader.SortDirection, True); + InvalidateToBottom(Node); + end; + end; + InvalidateCache(); + UpdateScrollBars(True); + end; + end + else + Result := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InvalidateChildren(Node: PVirtualNode; Recursive: Boolean); + +// Invalidates Node and its immediate children. +// If Recursive is True then all grandchildren are invalidated as well. +// The node itself is initialized if necessary and its child nodes are created (and initialized too if +// Recursive is True). + +var + Run: PVirtualNode; + +begin + if Assigned(Node) then + begin + if not (vsInitialized in Node.States) then + InitNode(Node); + InvalidateNode(Node); + if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then + InitChildren(Node); + Run := Node.FirstChild; + end + else + Run := FRoot.FirstChild; + + while Assigned(Run) do + begin + InvalidateNode(Run); + if Recursive then + InvalidateChildren(Run, True); + Run := Run.NextSibling; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InvalidateColumn(Column: TColumnIndex); + +// Invalidates the client area part of a column. + +var + R: TRect; + +begin + if (FUpdateCount = 0) and HandleAllocated and FHeader.Columns.IsValidColumn(Column) then + begin + R := ClientRect; + FHeader.Columns.GetColumnBounds(Column, R.Left, R.Right); + InvalidateRect(@R, False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.InvalidateNode(Node: PVirtualNode): TRect; + +// Initiates repaint of the given node and returns the just invalidated rectangle. + +begin + Assert(Assigned(Node), 'Node must not be nil.'); + Assert(GetCurrentThreadId = MainThreadId, 'UI controls may only be chnaged in UI thread.'); + // Reset height measured flag too to cause a re-issue of the OnMeasureItem event. + Exclude(Node.States, vsHeightMeasured); + if (FUpdateCount = 0) and HandleAllocated then + begin + Result := GetDisplayRect(Node, NoColumn, False); + InvalidateRect(@Result, False); + end + else + result := Rect(-1,-1,-1,-1); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InvalidateToBottom(Node: PVirtualNode); + +// Initiates repaint of client area starting at given node. If this node is not visible or not yet initialized +// then nothing happens. + +var + R: TRect; + +begin + if (FUpdateCount = 0) and HandleAllocated then + begin + if (Node = nil) or (Node = FRoot) then + Invalidate + else + if (vsInitialized in Node.States) and IsEffectivelyVisible[Node] then + begin + R := GetDisplayRect(Node, NoColumn, False); + if R.Top < ClientHeight then + begin + if (toChildrenAbove in FOptions.PaintOptions) and (vsExpanded in Node.States) then + Dec(R.Top, Node.TotalHeight + NodeHeight[Node]); + R.Bottom := ClientHeight; + InvalidateRect(@R, False); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.InvertSelection(VisibleOnly: Boolean); + +// Inverts the current selection (so nodes which are selected become unselected and vice versa). +// If VisibleOnly is True then only visible nodes are considered. + +var + Run: PVirtualNode; + NewSize: Integer; + NextFunction: TGetNextNodeProc; + TriggerChange: Boolean; + +begin + if not FSelectionLocked and (toMultiSelect in FOptions.SelectionOptions) then + begin + Run := FRoot.FirstChild; + ClearTempCache; + if VisibleOnly then + NextFunction := GetNextVisibleNoInit + else + NextFunction := GetNextNoInit; + while Assigned(Run) do + begin + if vsSelected in Run.States then + InternalRemoveFromSelection(Run) + else + InternalCacheNode(Run); + Run := NextFunction(Run); + end; + + // do some housekeeping + // Need to trigger the OnChange event from here if nodes were only deleted but not added. + TriggerChange := False; + NewSize := PackArray(FSelection, FSelectionCount); + if NewSize > -1 then + begin + FSelectionCount := NewSize; + SetLength(FSelection, FSelectionCount); + TriggerChange := True; + end; + if FTempNodeCount > 0 then + begin + AddToSelection(FTempNodeCache, FTempNodeCount); + ClearTempCache; + TriggerChange := False; + end; + Invalidate; + if TriggerChange then + Change(nil); + if Self.SelectedCount = 0 then + FNextNodeToSelect := nil;//Ensure that no other node is selected now + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsEditing: Boolean; + +begin + Result := tsEditing in FStates; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsMouseSelecting: Boolean; + +begin + Result := (tsDrawSelPending in FStates) or (tsDrawSelecting in FStates); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsStored_BackgroundOffsetXY(const Index: Integer): Boolean; +begin + case Index of + 0: + Result:= CompareValue(FBackgroundOffsetX, 0)<>EqualsValue; + 1: + Result:= CompareValue(FBackgroundOffsetY, 0)<>EqualsValue; + else + // Clear warning only + Result:= false; + RaiseVTError('Unknown index in TBaseVirtualTree.IsStored_BackgroundOffsetXY', 0); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsStored_BottomSpace: Boolean; +begin + Result:= CompareValue(FBottomSpace, 0)<>EqualsValue; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsStored_DefaultNodeHeight: Boolean; +begin + Result:= CompareValue(FDefaultNodeHeight, cInitialDefaultNodeHeight)<>EqualsValue; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsStored_Indent: Boolean; +begin + Result:= CompareValue(FIndent, 18)<>EqualsValue; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsStored_Margin: Boolean; +begin + Result:= CompareValue(FMargin, 4)<>EqualsValue; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsStored_TextMargin: Boolean; +begin + Result:= CompareValue(FTextMargin, cDefaultTextMargin) <> EqualsValue; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IsUpdating: Boolean; +// The tree does currently not update its window because a BeginUpdate has not yet ended. +begin + Exit(UpdateCount > 0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.IterateSubtree(StartNode: PVirtualNode; Callback: TVTGetNodeProc; Data: Pointer; + Filter: TVirtualNodeStates = []; DoInit: Boolean = False; ChildNodesOnly: Boolean = False): PVirtualNode; + +// Iterates through the all children and grandchildren etc. of Node (or the entire tree if Node = nil) +// and calls for each node the provided callback method (which must not be empty). +// Filter determines which nodes to consider (an empty set denotes all nodes). +// If DoInit is True then nodes which aren't initialized yet will be initialized. +// Note: During execution of the callback the application can set Abort to True. In this case the iteration is stopped +// and the last accessed node (the one on which the callback set Abort to True) is returned to the caller. +// Otherwise (no abort) nil is returned. + +var + Stop: PVirtualNode; + Abort: Boolean; + GetNextNode: TGetNextNodeProc; + WasIterating: Boolean; + +begin + Assert(StartNode <> FRoot, 'Node must not be the hidden root node.'); + + WasIterating := tsIterating in FStates; + DoStateChange([tsIterating]); + try + // prepare function to be used when advancing + if DoInit then + GetNextNode := GetNext + else + GetNextNode := GetNextNoInit; + + Abort := False; + Result := StartNode; + if Result = nil then + Stop := nil + else + begin + if not (vsInitialized in Result.States) and DoInit then + InitNode(Result); + + // The stopper does not need to be initialized since it is not taken into the enumeration. + Stop := Result.NextSibling; + if Stop = nil then + begin + Stop := Result; + repeat + Stop := Stop.Parent; + until (Stop = FRoot) or Assigned(Stop.NextSibling); + if Stop = FRoot then + Stop := nil + else + Stop := Stop.NextSibling; + end; + end; + + // Use first node if we start with the root. + if Result = nil then + Result := GetFirstNoInit; + + if Assigned(Result) then + begin + if not (vsInitialized in Result.States) and DoInit then + InitNode(Result); + + // Skip given node if only the child nodes are requested. + if ChildNodesOnly then + begin + if Result.ChildCount = 0 then + Result := nil + else if StartNode <> nil then + Result := GetNextNode(Result); + end; + + if Filter = [] then + begin + // unfiltered loop + while Assigned(Result) and (Result <> Stop) do + begin + Callback(Self, Result, Data, Abort); + if Abort then + Break; + Result := GetNextNode(Result); + end; + end + else + begin + // filtered loop + while Assigned(Result) and (Result <> Stop) do + begin + if Result.States * Filter = Filter then + Callback(Self, Result, Data, Abort); + if Abort then + Break; + Result := GetNextNode(Result); + end; + end; + end; + + if not Abort then + Result := nil; + finally + if not WasIterating then + DoStateChange([], [tsIterating]); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.LoadFromFile(const FileName: TFileName); + +var + FileStream: TFileStream; + +begin + FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); + try + LoadFromStream(FileStream); + finally + FileStream.Free; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.LoadFromStream(Stream: TStream); + +// Clears the current content of the tree and loads a new structure from the given stream. + +var + ThisID: TMagicID; + Version, + Count: Cardinal; + Node: PVirtualNode; + +begin + if not (toReadOnly in FOptions.MiscOptions) then + begin + Clear; + // Check first whether this is a stream we can read. + if Stream.Read(ThisID, SizeOf(TMagicID)) < SizeOf(TMagicID) then + RaiseVTError(SStreamTooSmall, hcTFStreamTooSmall); + + if (ThisID[0] = MagicID[0]) and + (ThisID[1] = MagicID[1]) and + (ThisID[2] = MagicID[2]) and + (ThisID[5] = MagicID[5]) then + begin + Version := Word(ThisID[3]); + if Version <= VTTreeStreamVersion then + begin + BeginUpdate; + try + if Version < 2 then + Count := MaxInt + else + Stream.ReadBuffer(Count, SizeOf(Count)); + + while (Stream.Position < Stream.Size) and (Count > 0) do + begin + System.Dec(Count); + Node := MakeNewNode; + InternalConnectNode(Node, FRoot, Self, amAddChildLast); + InternalAddFromStream(Stream, Version, Node); + end; + DoNodeCopied(nil); + if Assigned(FOnLoadTree) then + FOnLoadTree(Self, Stream); + finally + EndUpdate; + end; + end + else + RaiseVTError(SWrongStreamVersion, hcTFWrongStreamVersion); + end + else + RaiseVTError(SWrongStreamFormat, hcTFWrongStreamFormat); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.MeasureItemHeight(const Canvas: TCanvas; Node: PVirtualNode); + +// If the height of the given node has not yet been measured then do it now. + +var + NewNodeHeight: TDimension; + +begin + if not (vsHeightMeasured in Node.States) then + begin + Include(Node.States, vsHeightMeasured); + if (toVariableNodeHeight in FOptions.MiscOptions) then + begin + NewNodeHeight := Node.NodeHeight; + // Anonymous methods help to make this thread safe easily. + if (MainThreadId <> GetCurrentThreadId) then + begin + AtomicIncrement(FPendingSyncProcs); + TThread.Synchronize(nil, + procedure + begin + //swish:Decrement invoke refs + AtomicDecrement(FPendingSyncProcs); + DoMeasureItem(Canvas, Node, NewNodeHeight); + SetNodeHeight(Node, NewNodeHeight); + end + ) + end + else + begin + DoMeasureItem(Canvas, Node, NewNodeHeight); + SetNodeHeight(Node, NewNodeHeight); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.MoveTo(Node: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode; + ChildrenOnly: Boolean); + +// A simplified method to allow to move nodes to the root of another tree. + +begin + MoveTo(Node, Tree.FRoot, Mode, ChildrenOnly); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.MoveTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode; ChildrenOnly: Boolean); + +// Moves the given node (and all its children) to Target. Source must belong to the tree instance which calls this +// MoveTo method. Mode determines how to connect Source to Target. +// This method might involve a change of the tree if Target belongs to a different tree than Source. + +var + TargetTree: TBaseVirtualTree; + Allowed: Boolean; + NewNode: PVirtualNode; + Stream: TMemoryStream; + +begin + Assert(TreeFromNode(Source) = Self, 'The source tree must contain the source node.'); + + // When moving nodes then source and target must not be the same node unless only the source's children are + // moved and they are inserted before or after the node itself. + Allowed := (Source <> Target) or ((Mode in [amInsertBefore, amInsertAfter]) and ChildrenOnly); + + if Allowed and (Mode <> amNoWhere) and Assigned(Source) and (Source <> FRoot) and + not (toReadOnly in FOptions.MiscOptions) then + begin + // Assume that an empty destination means the root in this (the source) tree. + if Target = nil then + begin + TargetTree := Self; + Target := FRoot; + Mode := amAddChildFirst; + end + else + TargetTree := TreeFromNode(Target); + + if Target = TargetTree.FRoot then + begin + case Mode of + amInsertBefore: + Mode := amAddChildFirst; + amInsertAfter: + Mode := amAddChildLast; + end; + end; + + // Make sure the target node is initialized. + if not (vsInitialized in Target.States) then + TargetTree.InitNode(Target) + else + if (vsHasChildren in Target.States) and (Target.ChildCount = 0) then + TargetTree.InitChildren(Target); + + if TargetTree = Self then + begin + // Simple case: move node(s) within the same tree. + if Target = FRoot then + Allowed := DoNodeMoving(Source, nil) + else + Allowed := DoNodeMoving(Source, Target); + if Allowed then + begin + // Check first that Source is not added as new child to a target node which + // is already a child of Source. + // Consider the case Source and Target are the same node, but only child nodes are moved. + if (Source <> Target) and HasAsParent(Target, Source) then + RaiseVTError(SWrongMoveError, hcTFWrongMoveError); + + if not ChildrenOnly then + begin + // Disconnect from old location. + InternalDisconnectNode(Source, True); + // Connect to new location. + InternalConnectNode(Source, Target, Self, Mode); + DoNodeMoved(Source); + end + else + begin + // Only child nodes should be moved. Insertion order depends on move mode. + if Mode = amAddChildFirst then + begin + Source := Source.LastChild; + while Assigned(Source) do + begin + NewNode := Source.PrevSibling; + // Disconnect from old location. + InternalDisconnectNode(Source, True, False); + // Connect to new location. + InternalConnectNode(Source, Target, Self, Mode); + DoNodeMoved(Source); + Source := NewNode; + end; + end + else + begin + Source := Source.FirstChild; + while Assigned(Source) do + begin + NewNode := Source.NextSibling; + // Disconnect from old location. + InternalDisconnectNode(Source, True, False); + // Connect to new location. + InternalConnectNode(Source, Target, Self, Mode); + DoNodeMoved(Source); + Source := NewNode; + end; + end; + end; + end; + end + else + begin + // Difficult case: move node(s) to another tree. + // In opposition to node copying we ask only once if moving is allowed because + // we cannot take back a move once done. + if Target = TargetTree.FRoot then + Allowed := DoNodeMoving(Source, nil) + else + Allowed := DoNodeMoving(Source, Target); + + if Allowed then + begin + Stream := TMemoryStream.Create; + try + // Write all nodes into a temporary stream depending on the ChildrenOnly flag. + if not ChildrenOnly then + WriteNode(Stream, Source) + else + begin + Source := Source.FirstChild; + while Assigned(Source) do + begin + WriteNode(Stream, Source); + Source := Source.NextSibling; + end; + end; + // Now load the serialized nodes into the target node (tree). + TargetTree.BeginUpdate; + try + Stream.Position := 0; + while Stream.Position < Stream.Size do + begin + NewNode := TargetTree.MakeNewNode; + InternalConnectNode(NewNode, Target, TargetTree, Mode); + TargetTree.InternalAddFromStream(Stream, VTTreeStreamVersion, NewNode); + DoNodeMoved(NewNode); + end; + finally + TargetTree.EndUpdate; + end; + finally + Stream.Free; + end; + // finally delete original nodes + BeginUpdate; + try + if ChildrenOnly then + DeleteChildren(Source) + else + DeleteNode(Source); + finally + EndUpdate; + end; + end; + end; + + InvalidateCache; + if (FUpdateCount = 0) and Allowed then + begin + ValidateCache; + UpdateScrollBars(True); + Invalidate; + if TargetTree <> Self then + TargetTree.Invalidate; + end; + StructureChange(Source, crNodeMoved); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.PaintTree(TargetCanvas: TCanvas; Window: TRect; Target: TPoint; + PaintOptions: TVTInternalPaintOptions; PixelFormat: TPixelFormat); + +// This is the core paint routine of the tree. It is responsible for maintaining the paint cycles per node as well +// as coordinating drawing of the various parts of the tree image. +// TargetCanvas is the canvas to which to draw the tree image. This is usually the tree window itself but could well +// be a bitmap or printer canvas. +// Window determines which part of the entire tree image to draw. The full size of the virtual image is determined +// by GetTreeRect. +// Target is the position in TargetCanvas where to draw the tree part specified by Window. +// PaintOptions determines what of the tree to draw. For different tasks usually different parts need to be drawn, with +// a full image in the window, selected only nodes for a drag image etc. + +const + ImageKind: array[Boolean] of TVTImageKind = (ikNormal, ikSelected); + +var + DrawSelectionRect, + UseBackground, + ShowCheckImages, + UseColumns, + IsMainColumn: Boolean; + + IndentSize, + ButtonY: TDimension; // Y position of toggle button within the node's rect + LineImage: TLineImage; + PaintInfo: TVTPaintInfo; // all necessary information about a node to pass to the paint routines + + R, // the area of an entire node in its local coordinate + TargetRect, // the area of a node (part) in the target canvas + SelectionRect, // ordered rectangle used for drawing the selection focus rect + ClipRect: TRect; // area to which the canvas will be clipped when painting a node's content + NextColumn: TColumnIndex; + BaseOffset: TDimension; // top position of the top node to draw given in absolute tree coordinates + NodeBitmap: TBitmap; // small buffer to draw flicker free + MaximumRight, // maximum horizontal target position + MaximumBottom: TDimension; // maximum vertical target position + SelectLevel: Integer; // > 0 if current node is selected or child/grandchild etc. of a selected node + FirstColumn: TColumnIndex; // index of first column which is at least partially visible in the given window + + MaxRight, + ColLeft, + ColRight: TDimension; + + SavedTargetDC: Integer; + PaintWidth: TDimension; + CurrentNodeHeight: TDimension; + lEmptyListTextMargin: TDimension; + + CellIsTouchingClientRight: Boolean; + CellIsInLastColumn: Boolean; + ColumnIsFixed: Boolean; + +begin + if not (tsPainting in FStates) then + begin + DoStateChange([tsPainting]); + try + DoBeforePaint(TargetCanvas); + + if poUnbuffered in PaintOptions then + SavedTargetDC := SaveDC(TargetCanvas.Handle) + else + SavedTargetDC := 0; + + // Prepare paint info structure. + ZeroMemory(@PaintInfo, SizeOf(PaintInfo)); + + PaintWidth := Window.Right - Window.Left; + + if not (poUnbuffered in PaintOptions) then + begin + // Create small bitmaps and initialize default values. + // The bitmaps are used to paint one node at a time and to draw the result to the target (e.g. screen) in one step, + // to prevent flickering. + NodeBitmap := TBitmap.Create; + // For alpha blending we need the 32 bit pixel format. For other targets there might be a need for a certain + // pixel format (e.g. printing). + if ((FDrawSelectionMode = smBlendedRectangle) or (tsUseThemes in FStates) or + (toUseBlendedSelection in FOptions.PaintOptions)) then + NodeBitmap.PixelFormat := pf32Bit + else + NodeBitmap.PixelFormat := PixelFormat; + + NodeBitmap.Width := PaintWidth; + + // Make sure the buffer bitmap and target bitmap use the same transformation mode. + SetMapMode(NodeBitmap.Canvas.Handle, GetMapMode(TargetCanvas.Handle)); + PaintInfo.Canvas := NodeBitmap.Canvas; + end + else + begin + PaintInfo.Canvas := TargetCanvas; + NodeBitmap := nil; + end; + + // Lock the canvas to avoid that it gets freed on the way. + PaintInfo.Canvas.Lock; + try + // Prepare the current selection rectangle once. The corner points are absolute tree coordinates. + SelectionRect := OrderRect(FNewSelRect); + DrawSelectionRect := IsMouseSelecting and not IsRectEmpty(SelectionRect) and (GetKeyState(VK_LBUTTON) < 0); + + // R represents an entire node (all columns), but is a bit unprecise when it comes to + // trees without any column defined, because FRangeX only represents the maximum width of all + // nodes in the client area (not all defined nodes). There might be, however, wider nodes somewhere. Without full + // validation I cannot better determine the width, though. By using at least the control's width it is ensured + // that the tree is fully displayed on screen. + R := Rect(0, 0, Max(FRangeX, ClientWidth), 0); + + // For quick checks some intermediate variables are used. + UseBackground := (toShowBackground in FOptions.PaintOptions) and Assigned(FBackground.Graphic) and + (poBackground in PaintOptions); + ShowCheckImages := Assigned(FCheckImages) and (toCheckSupport in FOptions.MiscOptions); + UseColumns := FHeader.UseColumns; + + // Adjust paint options to tree settings. Hide selection if told so or the tree is unfocused. + if (toAlwaysHideSelection in FOptions.PaintOptions) or + (not Focused and (toHideSelection in FOptions.PaintOptions)) then + Exclude(PaintOptions, poDrawSelection); + if toHideFocusRect in FOptions.PaintOptions then + Exclude(PaintOptions, poDrawFocusRect); + + // Determine node to start drawing with. + BaseOffset := 0; + PaintInfo.Node := GetNodeAt(0, Window.Top, False, BaseOffset); + if PaintInfo.Node = nil then + BaseOffset := Window.Top; + + // Transform selection rectangle into node bitmap coordinates. + if DrawSelectionRect then + OffsetRect(SelectionRect, 0, -BaseOffset); + + // The target rectangle holds the coordinates of the exact area to blit in target canvas coordinates. + // It is usually smaller than an entire node and wanders while the paint loop advances. + MaximumRight := Target.X + (Window.Right - Window.Left); + MaximumBottom := Target.Y + (Window.Bottom - Window.Top); + + TargetRect := Rect(Target.X, Target.Y - (Window.Top - BaseOffset), MaximumRight, 0); + TargetRect.Bottom := TargetRect.Top; + TargetCanvas.Font := Self.Font; + + // This marker gets the index of the first column which is visible in the given window. + // This is needed for column based background colors. + FirstColumn := InvalidColumn; + + if Assigned(PaintInfo.Node) then + begin + + // ----- main node paint loop + while Assigned(PaintInfo.Node) do + begin + // Determine LineImage, SelectionLevel and IndentSize + SelectLevel := DetermineLineImageAndSelectLevel(PaintInfo.Node, LineImage); + IndentSize := Length(LineImage); + + // Initialize node if not already done. + if not (vsInitialized in PaintInfo.Node.States) then + InitNode(PaintInfo.Node); + if (vsSelected in PaintInfo.Node.States) and not (toChildrenAbove in FOptions.PaintOptions) then + System.Inc(SelectLevel); + + // Ensure the node's height is determined. + MeasureItemHeight(PaintInfo.Canvas, PaintInfo.Node); + + // Adjust the brush origin for dotted lines depending on the current source position. + // It is applied some lines later, as the canvas might get reallocated, when changing the node bitmap. + PaintInfo.BrushOrigin := Point(Window.Left and 1, BaseOffset and 1); + Inc(BaseOffset, PaintInfo.Node.NodeHeight); + + TargetRect.Bottom := TargetRect.Top + PaintInfo.Node.NodeHeight; + + // If poSelectedOnly is active then do the following stuff only for selected nodes or nodes + // which are children of selected nodes. + if (SelectLevel > 0) or not (poSelectedOnly in PaintOptions) then + begin + if not (poUnbuffered in PaintOptions) then + begin + // Adjust height of temporary node bitmap. + with NodeBitmap do + begin + if Height <> PaintInfo.Node.NodeHeight then + begin + // Avoid that the VCL copies the bitmap while changing its height. + Height := 0; + Height := PaintInfo.Node.NodeHeight; + SetCanvasOrigin(Canvas, Window.Left, 0); + end; + end; + end + else + begin + SetCanvasOrigin(PaintInfo.Canvas, -TargetRect.Left + Window.Left, -TargetRect.Top); + ClipCanvas(PaintInfo.Canvas, Rect(0, 0, TargetRect.Right - TargetRect.Left, + Min(TargetRect.Bottom - TargetRect.Top, MaximumBottom - TargetRect.Top))); // See issue #579 + end; + + // Set the origin of the canvas' brush. This depends on the node heights. + with PaintInfo do + SetBrushOrigin(Canvas, BrushOrigin.X, BrushOrigin.Y); + + CurrentNodeHeight := PaintInfo.Node.NodeHeight; + R.Bottom := CurrentNodeHeight; + + // Let application decide whether the node should normally be drawn or by the application itself. + if not DoBeforeItemPaint(PaintInfo.Canvas, PaintInfo.Node, R) then + begin + // Init paint options for the background painting. + PaintInfo.PaintOptions := PaintOptions; + + // The node background can contain a single color, a bitmap or can be drawn by the application. + ClearNodeBackground(PaintInfo, UseBackground, True, Rect(Window.Left, TargetRect.Top, Window.Right, + TargetRect.Bottom)); + + // Prepare column, position and node clipping rectangle. + PaintInfo.CellRect := R; + if UseColumns then + InitializeFirstColumnValues(PaintInfo); + + // Now go through all visible columns (there's still one run if columns aren't used). + with TVirtualTreeColumnsCracker(FHeader.Columns) do + begin + while ((PaintInfo.Column > InvalidColumn) or not UseColumns) + and (PaintInfo.CellRect.Left < Window.Right) do + begin + if UseColumns then + begin + PaintInfo.Column := PositionToIndex[PaintInfo.Position]; + if FirstColumn = InvalidColumn then + FirstColumn := PaintInfo.Column; + PaintInfo.BidiMode := Items[PaintInfo.Column].BiDiMode; + PaintInfo.Alignment := Items[PaintInfo.Column].Alignment; + end + else + begin + PaintInfo.Column := NoColumn; + PaintInfo.BidiMode := BidiMode; + PaintInfo.Alignment := FAlignment; + end; + GetOffSets(PaintInfo.Node, PaintInfo.Offsets, TVTElement.ofsText, PaintInfo.Column); + + PaintInfo.PaintOptions := PaintOptions; + with PaintInfo do + begin + if (tsEditing in FStates) and (Node = FFocusedNode) and + ((Column = FEditColumn) or not UseColumns) then + Exclude(PaintOptions, poDrawSelection); + if not UseColumns or + ((vsSelected in Node.States) and (toFullRowSelect in FOptions.SelectionOptions) and + (poDrawSelection in PaintOptions)) or + (coParentColor in Items[PaintInfo.Column].Options) or + ((coStyleColor in Items[PaintInfo.Column].Options) and VclStyleEnabled) + then + Exclude(PaintOptions, poColumnColor); + end; + IsMainColumn := PaintInfo.Column = FHeader.MainColumn; + + // Consider bidi mode here. In RTL context means left alignment actually right alignment and vice versa. + if PaintInfo.BidiMode <> bdLeftToRight then + ChangeBiDiModeAlignment(PaintInfo.Alignment); + + // Paint the current cell if it is marked as being visible or columns aren't used and + // if this cell belongs to the main column if only the main column should be drawn. + if (not UseColumns or (coVisible in Items[PaintInfo.Column].Options)) and + (not (poMainOnly in PaintOptions) or IsMainColumn) then + begin + AdjustPaintCellRect(PaintInfo, NextColumn); + + // Paint the cell only if it is in the current window. + if PaintInfo.CellRect.Right > Window.Left then + begin + with PaintInfo do + begin + // Fill in remaining values in the paint info structure. + NodeWidth := DoGetNodeWidth(Node, Column, Canvas); + + if ShowCheckImages and IsMainColumn then + begin + ImageInfo[iiCheck].Index := GetCheckImage(Node); + ImageInfo[iiCheck].Images := FCheckImages; + ImageInfo[iiCheck].Ghosted := False; + end + else + ImageInfo[iiCheck].Index := -1; + GetImageIndex(PaintInfo, ikState, iiState); + GetImageIndex(PaintInfo, ImageKind[vsSelected in Node.States], iiNormal); + + CalculateVerticalAlignments(PaintInfo, ButtonY); + // Take the space for the tree lines into account. + PaintInfo.AdjustImageCoordinates(); + if UseColumns then + begin + ClipRect := CellRect; + if poUnbuffered in PaintOptions then + begin + ClipRect.Left := Max(ClipRect.Left, Window.Left); + ClipRect.Right := Min(ClipRect.Right, Window.Right); + ClipRect.Top := Max(ClipRect.Top, Window.Top - (BaseOffset - CurrentNodeHeight)); + ClipRect.Bottom := ClipRect.Bottom - Max(TargetRect.Bottom - MaximumBottom, 0); + end; + ClipCanvas(Canvas, ClipRect); + end; + + // Paint the horizontal grid line. + if (poGridLines in PaintOptions) and (toShowHorzGridLines in FOptions.PaintOptions) then + begin + Canvas.Font.Color := FColors.GridLineColor; + if IsMainColumn and (FLineMode = lmBands) then + begin + if BidiMode = bdLeftToRight then + begin + DrawGridHLine(PaintInfo, CellRect.Left + PaintInfo.Offsets[ofsCheckBox] - fImagesMargin, CellRect.Right - LineWidth, CellRect.Bottom - LineWidth); + end + else + begin + DrawGridHLine(PaintInfo, CellRect.Left, CellRect.Right - IfThen(toFixedIndent in FOptions.PaintOptions, LineWidth, IndentSize) * FIndent - 1, CellRect.Bottom - LineWidth); + end; + end + else + DrawGridHLine(PaintInfo, CellRect.Left, CellRect.Right, CellRect.Bottom - LineWidth); + + Dec(CellRect.Bottom); + Dec(ContentRect.Bottom); + end; + + if UseColumns then + begin + ColumnIsFixed := coFixed in FHeader.Columns[Column].Options; + // Paint vertical grid line. + if (poGridLines in PaintOptions) and (toShowVertGridLines in FOptions.PaintOptions) then + begin + // These variables and the nested if conditions shall make the logic + // easier to understand. + CellIsTouchingClientRight := PaintInfo.CellRect.Right = ClientRect.Right; + CellIsInLastColumn := Position = TColumnPosition(Count - 1); + + // Don't draw if this is the last column and the header is in autosize mode. + if not ((hoAutoResize in FHeader.Options) and CellIsInLastColumn) then + begin + // We have to take spanned cells into account which we determine + // by checking if CellRect.Right equals the Window.Right. + // But since the PaintTree procedure is called twice in + // TBaseVirtualTree.Paint (i.e. for fixed columns and other columns. + // CellIsTouchingClientRight does not work for fixed columns.) + // we have to paint fixed column grid line anyway. + if not CellIsTouchingClientRight or ColumnIsFixed then + begin + if (BidiMode = bdLeftToRight) or not ColumnIsEmpty(Node, Column) then + begin + DrawGridVLine(PaintInfo, CellRect.Top, CellRect.Bottom, CellRect.Right - LineWidth, ColumnIsFixed and (NextColumn >= 0)); + end; + + Dec(CellRect.Right); + end; + end; + // Reduce the content rect size nonetheless to retain correct alignment + // relative to header content (especially if "PaintInfo.Alignment = alRightJustify"). + Dec(ContentRect.Right); + end// if poGridLines + else + begin + if ColumnIsFixed then + begin + if (BidiMode = bdLeftToRight) or not ColumnIsEmpty(Node, Column) then + begin + DrawGridVLine(PaintInfo, CellRect.Top, CellRect.Bottom, CellRect.Right - LineWidth, ColumnIsFixed and (NextColumn >= 0)); + end; + Dec(CellRect.Right); + end; + end//else + end; + + // Prepare background and focus rect for the current cell. + PrepareCell(PaintInfo, Window.Left, PaintWidth); + + // Some parts are only drawn for the main column. + if IsMainColumn then + begin + if (toShowTreeLines in FOptions.PaintOptions) and + (not (toHideTreeLinesIfThemed in FOptions.PaintOptions) or + not (tsUseThemes in FStates)) then + PaintTreeLines(PaintInfo, IfThen(toFixedIndent in FOptions.PaintOptions, 1, + IndentSize), LineImage); + // Show node button if allowed, if there child nodes and at least one of the child + // nodes is visible or auto button hiding is disabled. + if (toShowButtons in FOptions.PaintOptions) and (vsHasChildren in Node.States) and + not ((vsAllChildrenHidden in Node.States) and + (toAutoHideButtons in TreeOptions.AutoOptions)) and + ((toShowRoot in TreeOptions.PaintOptions) or (GetNodeLevel(Node) > 0)) + then + PaintNodeButton(Canvas, Node, Column, CellRect, Offsets[ofsToggleButton], ButtonY, BidiMode); // Relative X position of toggle button is needed for proper BiDi calculation + + if ImageInfo[iiCheck].Index > -1 then + PaintCheckImage(Canvas, PaintInfo.ImageInfo[iiCheck], vsSelected in PaintInfo.Node.States); + end; + + if ImageInfo[iiState].Index > -1 then + PaintImage(PaintInfo, iiState, False); + if ImageInfo[iiNormal].Index > -1 then + PaintImage(PaintInfo, iiNormal, True); + + // Now let descendants or applications draw whatever they want, + // but don't draw the node if it is currently being edited. + if not ((tsEditing in FStates) and (Node = FFocusedNode) and + ((Column = FEditColumn) or not UseColumns)) then + DoPaintNode(PaintInfo); + + DoAfterCellPaint(Canvas, Node, Column, CellRect); + end; + end; + + // leave after first run if columns aren't used + if not UseColumns then + Break; + end + else + NextColumn := GetNextVisibleColumn(PaintInfo.Column); + + SelectClipRgn(PaintInfo.Canvas.Handle, 0); + // Stop column loop if there are no further columns in the given window. + if (PaintInfo.CellRect.Left >= Window.Right) or (NextColumn = InvalidColumn) then + Break; + + // Move on to next column which might not be the one immediately following the current one + // because of auto span feature. + PaintInfo.Position := Items[NextColumn].Position; + + // Move clip rectangle and continue. + if coVisible in Items[NextColumn].Options then + with PaintInfo do + begin + TVirtualTreeColumnCracker(Items[NextColumn]).GetAbsoluteBounds(CellRect.Left, CellRect.Right); + CellRect.Bottom := Node.NodeHeight; + ContentRect.Bottom := Node.NodeHeight; + end; + end; + end; + + // This node is finished, notify descendants/application. + with PaintInfo do + begin + DoAfterItemPaint(Canvas, Node, R); + + // Final touch for this node: mark it if it is the current drop target node. + if (Node = FDropTargetNode) and (toShowDropmark in FOptions.PaintOptions) and + (poDrawDropMark in PaintOptions) then + DoPaintDropMark(Canvas, Node, R); + end; + end; // if not DoBeforeItemPaint() (no custom drawing) + + + with PaintInfo.Canvas do + begin + if DrawSelectionRect then + begin + PaintSelectionRectangle(PaintInfo.Canvas, Window.Left, SelectionRect, Rect(0, 0, PaintWidth, + CurrentNodeHeight)); + end; + + // Put the constructed node image onto the target canvas. + if not (poUnbuffered in PaintOptions) then + with NodeBitmap do + BitBlt(TargetCanvas.Handle, TargetRect.Left, TargetRect.Top, TargetRect.Width, TargetRect.Height, Canvas.Handle, Window.Left, 0, SRCCOPY); + end; + end; + + Inc(TargetRect.Top, PaintInfo.Node.NodeHeight); + if TargetRect.Top >= MaximumBottom then + Break; + + // Keep selection rectangle coordinates in sync. + if DrawSelectionRect then + OffsetRect(SelectionRect, 0, -PaintInfo.Node.NodeHeight); + + // Advance to next visible node. + PaintInfo.Node := GetNextVisible(PaintInfo.Node, True); + end; + end; + + // Erase rest of window not covered by a node. + if TargetRect.Top < MaximumBottom then + begin + // Keep the horizontal target position to determine the selection rectangle offset later (if necessary). + BaseOffset := Target.X; + Target := TargetRect.TopLeft; + R := Rect(TargetRect.Left, 0, TargetRect.Left, MaximumBottom - Target.Y); + TargetRect := Rect(0, 0, MaximumRight - Target.X, MaximumBottom - Target.Y); + + if not (poUnbuffered in PaintOptions) then + begin + // Avoid unnecessary copying of bitmap content. This will destroy the DC handle too. + NodeBitmap.Height := 0; + NodeBitmap.PixelFormat := pf32Bit; + NodeBitmap.SetSize(TargetRect.Right - TargetRect.Left, TargetRect.Bottom - TargetRect.Top); + end; + + // Call back application/descendants whether they want to erase this area. + if not DoPaintBackground(PaintInfo.Canvas, TargetRect) then + begin + if UseBackground then + begin + SetCanvasOrigin(PaintInfo.Canvas, 0, 0); + if toStaticBackground in TreeOptions.PaintOptions then + StaticBackground(FBackground, PaintInfo.Canvas, Target, TargetRect, FColors.BackGroundColor) + else + TileBackground(FBackground, PaintInfo.Canvas, Target, TargetRect, FColors.BackGroundColor); + end + else + begin + // Consider here also colors of the columns. + SetCanvasOrigin(PaintInfo.Canvas, Target.X, 0); // This line caused issue #313 when it was placed above the if-statement + if UseColumns then + begin + with FHeader.Columns do + begin + // If there is no content in the tree then the first column has not yet been determined. + if FirstColumn = InvalidColumn then + begin + FirstColumn := GetFirstVisibleColumn; + repeat + if FirstColumn <> InvalidColumn then + begin + R.Left := Items[FirstColumn].Left; + R.Right := R.Left + Items[FirstColumn].Width; + if R.Right > TargetRect.Left then + Break; + FirstColumn := GetNextVisibleColumn(FirstColumn); + end; + until FirstColumn = InvalidColumn; + end + else + begin + R.Left := Items[FirstColumn].Left; + R.Right := R.Left + Items[FirstColumn].Width; + end; + + // Initialize MaxRight. + MaxRight := Target.X - 1; + + PaintInfo.Canvas.Font.Color := FColors.GridLineColor; + while (FirstColumn <> InvalidColumn) and (MaxRight < TargetRect.Right + Target.X) do + begin + // Determine left and right coordinate of the current column + ColLeft := Items[FirstColumn].Left; + ColRight := (ColLeft + Items[FirstColumn].Width); + + // Check wether this column needs to be painted at all. + if (ColRight >= MaxRight) then + begin + R.Left := MaxRight; // Continue where we left off + R.Right := ColRight; // Paint to the right of the column + MaxRight := ColRight; // And record were to start the next column. + + if (poGridLines in PaintOptions) and + (toFullVertGridLines in FOptions.PaintOptions) and + (toShowVertGridLines in FOptions.PaintOptions) and + (not (hoAutoResize in FHeader.Options) or (Cardinal(FirstColumn) < TColumnPosition(Count - 1))) then + begin + DrawGridVLine(PaintInfo, R.Top, R.Bottom, R.Right - 1); + Dec(R.Right); + end; + + if not (coParentColor in Items[FirstColumn].Options) then + PaintInfo.Canvas.Brush.Color := Items[FirstColumn].Color + else + PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor; + PaintInfo.Canvas.FillRect(R); + end; + FirstColumn := GetNextVisibleColumn(FirstColumn); + end; + + // Erase also the part of the tree not covert by a column. + if R.Right < TargetRect.Right + Target.X then + begin + R.Left := R.Right; + R.Right := TargetRect.Right + Target.X; + // Prevent erasing the last vertical grid line. + if (poGridLines in PaintOptions) and + (toFullVertGridLines in FOptions.PaintOptions) and (toShowVertGridLines in FOptions.PaintOptions) and + (not (hoAutoResize in FHeader.Options)) then + Inc(R.Left); + PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor; + PaintInfo.Canvas.FillRect(R); + end; + end; + SetCanvasOrigin(PaintInfo.Canvas, 0, 0); + end + else + begin + // No columns nor bitmap background. Simply erase it with the tree color. + SetCanvasOrigin(PaintInfo.Canvas, 0, 0); + PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor; + PaintInfo.Canvas.FillRect(TargetRect); + end; + end; + end; + SetCanvasOrigin(PaintInfo.Canvas, 0, 0); + + if DrawSelectionRect then + begin + R := OrderRect(FNewSelRect); + // Remap the selection rectangle to the current window of the tree. + // Since Target has been used for other tasks BaseOffset got the left extent of the target position here. + OffsetRect(R, -Target.X + BaseOffset - Window.Left, -Target.Y + FOffsetY); + SetBrushOrigin(PaintInfo.Canvas, 0, Target.X and 1); + PaintSelectionRectangle(PaintInfo.Canvas, 0, R, TargetRect); + end; + + if not (poUnBuffered in PaintOptions) then + with Target, NodeBitmap do + BitBlt(TargetCanvas.Handle, X, Y, Width, Height, Canvas.Handle, 0, 0, SRCCOPY); + end; + finally + PaintInfo.Canvas.Unlock; + if poUnbuffered in PaintOptions then + RestoreDC(TargetCanvas.Handle, SavedTargetDC) + else + NodeBitmap.Free; + end;//try..finally + + if (FEmptyListMessage <> '') and ((ChildCount[nil] = 0) or (GetFirstVisible = nil)) then + begin + // output a message if no items are to display + Canvas.Font := Self.Font; + Canvas.Font.Size := Round(Canvas.Font.Size * 1.25); // Use slightly larger font to attract awareness of user, there is enough space ince the list is empty. + SetBkMode(TargetCanvas.Handle, TRANSPARENT); + lEmptyListTextMargin := ScaledPixels(Max(cDefaultTextMargin, Self.TextMargin) * 2); // Since the list is empty and the font is slightly larger make sure text id not too close at the edges so that it looks good. + R.Left := OffSetX + lEmptyListTextMargin; + R.Top := lEmptyListTextMargin; + R.Right := R.Left + Width - lEmptyListTextMargin; + R.Bottom := Height - lEmptyListTextMargin; + TargetCanvas.Font.Color := StyleServices.GetStyleFontColor(TStyleFont.sfTreeItemTextDisabled);//clGrayText; + TargetCanvas.TextRect(R, FEmptyListMessage, [tfNoClip, tfLeft, tfWordBreak, tfExpandTabs]); + end; + + DoAfterPaint(TargetCanvas); + finally + DoStateChange([], [tsPainting]); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.PrepareDragImage(HotSpot: TPoint; const DataObject: TVTDragDataObject); + +// Initiates an image drag operation. HotSpot is the position of the mouse in client coordinates. + +var + PaintOptions: TVTInternalPaintOptions; + TreeRect, + PaintRect: TRect; + LocalSpot, + PaintTarget: TPoint; + lDragImage: TVTDragImage; // drag image management + Image: TBitmap; + +begin + if CanShowDragImage then + begin + lDragImage := TVTDragImage.Create(Self); + try + // Determine the drag rectangle which is a square around the hot spot. Operate in virtual tree space. + LocalSpot := HotSpot; + Dec(LocalSpot.X, -FEffectiveOffsetX); + Dec(LocalSpot.Y, FOffsetY); + TreeRect := Rect(LocalSpot.X - FDragWidth div 2, LocalSpot.Y - FDragHeight div 2, LocalSpot.X + FDragWidth div 2, + LocalSpot.Y + FDragHeight div 2); + + // Check that we have a valid rectangle. + PaintRect := TreeRect; + if TreeRect.Left < 0 then + begin + PaintTarget.X := -TreeRect.Left; + PaintRect.Left := 0; + end + else + PaintTarget.X := 0; + if TreeRect.Top < 0 then + begin + PaintTarget.Y := -TreeRect.Top; + PaintRect.Top := 0; + end + else + PaintTarget.Y := 0; + + Image := TBitmap.Create; + with Image do + try + PixelFormat := pf32Bit; + SetSize(TreeRect.Right - TreeRect.Left, TreeRect.Bottom - TreeRect.Top); + // Erase the entire image with the color key value, for the case not everything + // in the image is covered by the tree image. + Canvas.Brush.Color := FColors.BackGroundColor; + Canvas.FillRect(Rect(0, 0, Width, Height)); + + PaintOptions := [poDrawSelection, poSelectedOnly]; + if FDragImageKind = diMainColumnOnly then + Include(PaintOptions, poMainOnly); + PaintTree(Image.Canvas, PaintRect, PaintTarget, PaintOptions); + + // Once we have got the drag image we can convert all necessary coordinates into screen space. + OffsetRect(TreeRect, -FEffectiveOffsetX, FOffsetY); + HotSpot.X := Width div 2; + HotSpot.Y := Height div 2; + + lDragImage.PrepareDrag(Image, HotSpot, DataObject, FColors.BackGroundColor); + finally + Image.Free; + end; + finally + lDragImage.Free; + end; // try..finally + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.Print(Printer: TPrinter; PrintHeader: Boolean); + +var + SaveTreeFont: TFont; // Remembers the tree's current font. + SaveHeaderFont: TFont; // Remembers the header's current font. + ImgRect, // Describes the dimensions of Image. + TreeRect, // The total VTree dimensions. + DestRect, // Dimensions of PrinterImage. + SrcRect: TRect; // Clip dimensions from Image -> PrinterImage + P: TPoint; // Used by PaintTree. + Options: TVTInternalPaintOptions; // Used by PaintTree. + Image, // Complete Tree is drawn to this image. + PrinterImage: TBitmap; // This is the image that gets printed. + SaveColor: TColor; // Remembers the VTree Color. + pTxtHeight, // Height of font in the TPrinter.Canvas + vTxtHeight, // Height of font in the VTree Canvas + vPageWidth, + vPageHeight, // Printer height in VTree resolution + xPageNum, yPageNum, // # of pages (except the occasional last one) + xPage, yPage: Integer; // Loop counter + Scale: Extended; // Scale factor between Printer Canvas and VTree Canvas + LogFont: TLogFont; + +begin + if Assigned(Printer) then + begin + BeginUpdate; + + // Grid lines are the only parts which are desirable when printing. + Options := [poGridLines]; + + // Remember the tree font. + SaveTreeFont := TFont.Create; + SaveTreeFont.Assign(Font); + // Create a new font for printing which does not use clear type output (but is antialiased, if possible) + // and which has the highest possible quality. + GetObject(Font.Handle, SizeOf(TLogFont), @LogFont); + LogFont.lfQuality := ANTIALIASED_QUALITY; + Font.Handle := CreateFontIndirect(LogFont); + + // Create an image that will hold the complete VTree + Image := TBitmap.Create; + Image.PixelFormat := pf32Bit; + PrinterImage := nil; + try + TreeRect := GetTreeRect; + + Image.Width := TreeRect.Right - TreeRect.Left; + P := Point(0, 0); + if (hoVisible in FHeader.Options) and PrintHeader then + begin + Inc(TreeRect.Bottom, FHeader.Height); + Inc(P.Y, FHeader.Height); + end; + Image.Height := TreeRect.Bottom - TreeRect.Top; + + ImgRect.Left := 0; + ImgRect.Top := 0; + ImgRect.Right := Image.Width; + + // Force the background to white color during the rendering. + SaveColor := FColors.BackGroundColor; + Color := clWhite; + // Print header if it is visible. + if (hoVisible in FHeader.Options) and PrintHeader then + begin + SaveHeaderFont := TFont.Create; + try + SaveHeaderFont.Assign(FHeader.Font); + // Create a new font for printing which does not use clear type output (but is antialiased, if possible) + // and which has the highest possible quality. + GetObject(FHeader.Font.Handle, SizeOf(TLogFont), @LogFont); + LogFont.lfQuality := ANTIALIASED_QUALITY; + FHeader.Font.Handle := CreateFontIndirect(LogFont); + ImgRect.Bottom := FHeader.Height; + FHeader.Columns.PaintHeader(Image.Canvas.Handle, ImgRect, 0); + FHeader.Font := SaveHeaderFont; + finally + SaveHeaderFont.Free; + end; + end; + // The image's height is already adjusted for the header if it is visible. + ImgRect.Bottom := Image.Height; + + PaintTree(Image.Canvas, ImgRect, P, Options, pf32Bit); + Color := SaveColor; + + // Activate the printer + Printer.BeginDoc; + Printer.Canvas.Font := Font; + + // Now we can calculate the scaling : + pTxtHeight := Printer.Canvas.TextHeight('Tj'); + vTxtHeight := Canvas.TextHeight('Tj'); + + Scale := pTxtHeight / vTxtHeight; + + // Create an Image that has the same dimensions as the printer canvas but + // scaled to the VTree resolution: + PrinterImage := TBitmap.Create; + + vPageHeight := Round(Printer.PageHeight / Scale); + vPageWidth := Round(Printer.PageWidth / Scale); + + // We do a minumum of one page. + xPageNum := Trunc(Image.Width / vPageWidth); + yPageNum := Trunc(Image.Height / vPageHeight); + + PrinterImage.SetSize(vPageWidth, vPageHeight); + + // Split vertically: + for yPage := 0 to yPageNum do + begin + DestRect.Left := 0; + DestRect.Top := 0; + DestRect.Right := PrinterImage.Width; + DestRect.Bottom := PrinterImage.Height; + + // Split horizontally: + for xPage := 0 to xPageNum do + begin + SrcRect.Left := vPageWidth * xPage; + SrcRect.Top := vPageHeight * yPage; + SrcRect.Right := vPageWidth * xPage + PrinterImage.Width; + SrcRect.Bottom := SrcRect.Top + vPageHeight; + + // Clear the image + PrinterImage.Canvas.Brush.Color := clWhite; + PrinterImage.Canvas.FillRect(Rect(0, 0, PrinterImage.Width, PrinterImage.Height)); + PrinterImage.Canvas.CopyRect(DestRect, Image.Canvas, SrcRect); + PrtStretchDrawDIB(Printer.Canvas, Rect(0, 0, Printer.PageWidth, Printer.PageHeight - 1), PrinterImage); + if xPage <> xPageNum then + Printer.NewPage; + end; + if yPage <> yPageNum then + Printer.NewPage; + end; + + // Restore tree font. + Font := SaveTreeFont; + SaveTreeFont.Free; + Printer.EndDoc; + finally + PrinterImage.Free; + Image.Free; + EndUpdate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ProcessDrop(const DataObject: TVTDragDataObject; TargetNode: PVirtualNode; var Effect: Integer; + Mode: TVTNodeAttachMode): Boolean; + +// Recreates the (sub) tree structure serialized into memory and provided by DataObject. The new nodes are attached to +// the passed node or FRoot if TargetNode is nil. +// Returns True on success, i.e. the CF_VIRTUALTREE format is supported by the data object and the structure could be +// recreated, otherwise False. + +var + Source: TBaseVirtualTree; + +begin + Result := False; + if Mode = amNoWhere then + Effect := DROPEFFECT_NONE + else + begin + BeginUpdate; + // try to get the source tree of the operation + Source := TVTDragManager.GetTreeFromDataObject(DataObject); + if Assigned(Source) then + Source.BeginUpdate; + try + try + // Before adding the new nodes try to optimize the operation if source and target tree reside in + // the same application and operation is a move. + if ((Effect and DROPEFFECT_MOVE) <> 0) and Assigned(Source) then + begin + // If both copy and move are specified then prefer a copy because this is not destructing. + Result := ProcessOLEData(Source, DataObject, TargetNode, Mode, (Effect and DROPEFFECT_COPY) = 0); + // Since we made an optimized move or a copy there's no reason to act further after DoDragging returns. + Effect := DROPEFFECT_NONE; + end + else + // Act only if move or copy operation is requested. + if (Effect and (DROPEFFECT_MOVE or DROPEFFECT_COPY)) <> 0 then + Result := ProcessOLEData(Source, DataObject, TargetNode, Mode, False) + else + Result := False; + except + Effect := DROPEFFECT_NONE; + end; + finally + if Assigned(Source) then + Source.EndUpdate; + EndUpdate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +type + // needed to handle OLE global memory objects + TOLEMemoryStream = class(TCustomMemoryStream) + public + function Write(const Buffer; Count: Integer): Integer; override; + end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TOLEMemoryStream.Write(const Buffer; Count: Integer): Integer; + +begin + raise EStreamError.CreateRes(PResStringRec(@SCantWriteResourceStreamError)); +end; + +//----------------- TBaseVirtualTree ----------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoDrawHint(Canvas: TCanvas; Node: PVirtualNode; R: + TRect; Column: TColumnIndex); + +begin + if Assigned(FOnDrawHint) then + FOnDrawHint(Self, Canvas, Node, R, Column); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoGetHintSize(Node: PVirtualNode; Column: + TColumnIndex; var R: TRect); + +begin + if Assigned(FOnGetHintSize) then + FOnGetHintSize(Self, Node, Column, R); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.DoGetHintKind(Node: PVirtualNode; Column: + TColumnIndex; var Kind: TVTHintKind); + +begin + if Assigned(FOnGetHintKind) then + FOnGetHintKind(Self, Node, Column, Kind) + else + Kind := DefaultHintKind; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.GetDefaultHintKind: TVTHintKind; + +begin + Result := vhkText; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ProcessOLEData(Source: TBaseVirtualTree; const DataObject: IDataObject; TargetNode: PVirtualNode; + Mode: TVTNodeAttachMode; Optimized: Boolean): Boolean; + +// Recreates the (sub) tree structure serialized into memory and provided by DataObject. The new nodes are attached to +// the passed node or FRoot if TargetNode is nil according to Mode. Optimized can be set to True if the entire operation +// happens within the same process (i.e. sender and receiver of the OLE operation are located in the same process). +// Optimize = True makes only sense if the operation to carry out is a move hence it is also the indication of the +// operation to be done here. Source is the source of the OLE data and only of use (and usually assigned) when +// an OLE operation takes place in the same application. +// Returns True on success, i.e. the CF_VIRTUALTREE format is supported by the data object and the structure could be +// recreated, otherwise False. + +var + Medium: TStgMedium; + Stream: TStream; + Data: Pointer; + Node: PVirtualNode; + Nodes: TNodeArray; + I: Integer; + Res: HRESULT; + ChangeReason: TChangeReason; + +begin + Nodes := nil; + // Check the data format available by the data object. + with StandardOLEFormat do + begin + // Read best format. + cfFormat := CF_VIRTUALTREE; + end; + Result := DataObject.QueryGetData(StandardOLEFormat) = S_OK; + if Result and not (toReadOnly in FOptions.MiscOptions) then + begin + BeginUpdate; + Result := False; + try + if TargetNode = nil then + TargetNode := FRoot; + if TargetNode = FRoot then + begin + case Mode of + amInsertBefore: + Mode := amAddChildFirst; + amInsertAfter: + Mode := amAddChildLast; + end; + end; + + // Optimized means source is known and in the same process so we can access its pointers, which avoids duplicating + // the data while doing a serialization. Can only be used with cut'n paste and drag'n drop with move effect. + if Optimized then + begin + if tsOLEDragging in Source.FStates then + Nodes := Source.FDragSelection + else + Nodes := Source.GetSortedCutCopySet(True); + + if Mode in [amInsertBefore,amAddChildLast] then + begin + for I := 0 to High(Nodes) do + if not HasAsParent(TargetNode, Nodes[I]) then + Source.MoveTo(Nodes[I], TargetNode, Mode, False); + end + else + begin + for I := High(Nodes) downto 0 do + if not HasAsParent(TargetNode, Nodes[I]) then + Source.MoveTo(Nodes[I], TargetNode, Mode, False); + end; + Result := True; + end + else + begin + if Source = Self then + ChangeReason := crNodeCopied + else + ChangeReason := crNodeAdded; + Res := DataObject.GetData(StandardOLEFormat, Medium); + if Res = S_OK then + begin + case Medium.tymed of + TYMED_ISTREAM, // IStream interface + TYMED_HGLOBAL: // global memory block + begin + Stream := nil; + if Medium.tymed = TYMED_ISTREAM then + Stream := TOLEStream.Create(IUnknown(Medium.stm) as IStream) + else + begin + Data := GlobalLock(Medium.hGlobal); + if Assigned(Data) then + begin + // Get the total size of data to retrieve. + I := PCardinal(Data)^; + Inc(PCardinal(Data)); + Stream := TOLEMemoryStream.Create; + TOLEMemoryStream(Stream).SetPointer(Data, I); + end; + end; + + if Assigned(Stream) then + try + while Stream.Position < Stream.Size do + begin + Node := MakeNewNode; + InternalConnectNode(Node, TargetNode, Self, Mode); + InternalAddFromStream(Stream, VTTreeStreamVersion, Node); + // This seems a bit strange because of the callback for granting to add the node + // which actually comes after the node has been added. The reason is that the node must + // contain valid data otherwise I don't see how the application can make a funded decision. + if not DoNodeCopying(Node, TargetNode) then + begin + DeleteNode(Node); + end + else + begin + DoNodeCopied(Node); + StructureChange(Node, ChangeReason); + // In order to maintain the same node order when restoring nodes in the case of amInsertAfter + // we have to move the reference node continously. Othwise we would end up with reversed node order. + if Mode = amInsertAfter then + TargetNode := Node; + end; + end; + Result := True; + finally + Stream.Free; + if Medium.tymed = TYMED_HGLOBAL then + GlobalUnlock(Medium.hGlobal); + end; + end; + end; + ReleaseStgMedium(Medium); + end; + end; + finally + EndUpdate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ReinitChildren(Node: PVirtualNode; Recursive: + Boolean; ForceReinit: Boolean = False); + +// Forces all child nodes of Node to be reinitialized. +// If Recursive is True then also the grandchildren are reinitialized. + +var + Run: PVirtualNode; + +begin + if Assigned(Node) then + begin + InitChildren(Node); + Run := Node.FirstChild; + end + else + begin + InitChildren(FRoot); + Run := FRoot.FirstChild; + end; + + while Assigned(Run) do + begin + ReinitNode(Run, Recursive, ForceReinit); + Run := Run.NextSibling; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ReinitNode(Node: PVirtualNode; Recursive: Boolean; + ForceReinit: Boolean = False); + +// Reinitializes Node if it has previously been initialized +// If Recursive, initialized children are also re-initialized recursively +// If ForceReinit, Node is always initialized and if Recursive children +// are always re-initialized as well +// InitNode is called with ivsReInit in InitialStates, if the Node has already +// been initialized. + +begin + if Assigned(Node) and (Node <> FRoot) then + begin + // Remove dynamic styles. + Node.States := Node.States - [vsChecking, vsCutOrCopy, vsDeleting, vsHeightMeasured]; + if (vsInitialized in Node.States) or ForceReinit then + InitNode(Node); + end + else if not Assigned(Node) then + Node := FRoot; + + // Prevent previoulsy uninitilaized children from being initialized + // unless ForceReinit is True. Issue #1145 + if Recursive and (ForceReinit or (Node.ChildCount > 0)) then + ReinitChildren(Node, True, ForceReinit); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.RepaintNode(Node: PVirtualNode); + +// Causes an immediate repaint of the given node. + +var + R: Trect; + +begin + if Assigned(Node) and (Node <> FRoot) then + begin + R := GetDisplayRect(Node, NoColumn, False); + RedrawWindow(@R, 0, RDW_INVALIDATE or RDW_UPDATENOW or RDW_NOERASE or RDW_VALIDATE or RDW_NOCHILDREN); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ResetNode(Node: PVirtualNode); + +// Deletes all children of the given node and marks it as being uninitialized. + +begin + DoCancelEdit; + if (Node = nil) or (Node = FRoot) then + Clear + else + begin + DoReset(Node); + DeleteChildren(Node); + // Remove initialized and other dynamic styles, keep persistent styles. + Node.States := Node.States - [vsInitialized, vsChecking, vsCutOrCopy, vsDeleting, vsHasChildren, vsExpanded, + vsHeightMeasured]; + InvalidateNode(Node); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SaveToFile(const FileName: TFileName); + +// Saves the entire content of the tree into a file (see further notes in SaveToStream). + +var + FileStream: TFileStream; + +begin + FileStream := TFileStream.Create(FileName, fmCreate); + try + SaveToStream(FileStream); + finally + FileStream.Free; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SaveToStream(Stream: TStream; Node: PVirtualNode = nil); + +// Saves Node and all its children to Stream. If Node is nil then all top level nodes will be stored. +// Note: You should be careful about assuming what is actually saved. The problem here is that we are dealing with +// virtual data. The tree can so not know what it has to save. The only fact we reliably know is the tree's +// structure. To be flexible for future enhancements as well as unknown content (unknown to the tree class which +// is saving/loading the stream) a chunk based approach is used here. Every tree class handles only those +// chunks which are not handled by an anchestor class and are known by the class. +// +// The base tree class saves only the structure of the tree along with application provided data. descendants may +// optionally add their own chunks to store additional information. See: WriteChunks. + +var + Count: Cardinal; + +begin + Stream.Write(MagicID, SizeOf(MagicID)); + if Node = nil then + begin + // Keep number of top level nodes for easy restauration. + Count := FRoot.ChildCount; + Stream.WriteBuffer(Count, SizeOf(Count)); + + // Save entire tree here. + Node := FRoot.FirstChild; + while Assigned(Node) do + begin + WriteNode(Stream, Node); + Node := Node.NextSibling; + end; + end + else + begin + Count := 1; + Stream.WriteBuffer(Count, SizeOf(Count)); + WriteNode(Stream, Node); + end; + if Assigned(FOnSaveTree) then + FOnSaveTree(Self, Stream); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ScrollIntoView(Node: PVirtualNode; Center: Boolean; Horizontally: Boolean = False): Boolean; + +// Scrolls the tree so that the given node is in the client area and returns True if the tree really has been +// scrolled (e.g. to avoid further updates) else returns False. If extened focus is enabled then the tree will also +// be horizontally scrolled if needed. +// Note: All collapsed parents of the node are expanded. + +var + R: TRect; + Run: PVirtualNode; + UseColumns, + HScrollBarVisible: Boolean; + OldOffsetY: TDimension; + ScrolledVertically, + ScrolledHorizontally: Boolean; + +begin + ScrolledVertically := False; + ScrolledHorizontally := False; + + if Assigned(Node) and (Node <> FRoot) and HandleAllocated then // We don't want to create the handle if it has not yet been created, see issue #897 + begin + // Make sure all parents of the node are expanded. + Run := Node.Parent; + while Run <> FRoot do + begin + if not (vsExpanded in Run.States) then + ToggleNode(Run); + Run := Run.Parent; + end; + UseColumns := FHeader.UseColumns; + if UseColumns and FHeader.Columns.IsValidColumn(FFocusedColumn) then + R := GetDisplayRect(Node, FFocusedColumn, not (toGridExtensions in FOptions.MiscOptions)) + else + R := GetDisplayRect(Node, NoColumn, not (toGridExtensions in FOptions.MiscOptions)); + + // The returned rectangle can never be empty after the expand code above. + // 1) scroll vertically + OldOffsetY := FOffsetY; + if R.Top < 0 then + begin + if Center then + SetOffsetY(FOffsetY - R.Top + Divide(ClientHeight, 2)) + else + SetOffsetY(FOffsetY - R.Top); + end + else + if (R.Bottom > ClientHeight) or Center then + begin + HScrollBarVisible := (ScrollBarOptions.ScrollBars in [System.UITypes.TScrollStyle.ssBoth, System.UITypes.TScrollStyle.ssHorizontal]) and + (ScrollBarOptions.AlwaysVisible or (FRangeX > ClientWidth)); + if Center then + SetOffsetY(FOffsetY - R.Bottom + Divide(ClientHeight, 2)) + else + SetOffsetY(FOffsetY - R.Bottom + ClientHeight); + // When scrolling up and the horizontal scroll appears because of the operation + // then we have to move up the node the horizontal scrollbar's height too + // in order to avoid that the scroll bar hides the node which we wanted to have in view. + if not UseColumns and not HScrollBarVisible and (FRangeX > ClientWidth) then + SetOffsetY(FOffsetY - GetSystemMetrics(SM_CYHSCROLL)); + end; + ScrolledVertically := OldOffsetY <> FOffsetY; + + if Horizontally then + // 2) scroll horizontally + // Center only if there is enough space for the focused column, otherwise left align, see issue #397. + ScrolledHorizontally := ScrollIntoView(FFocusedColumn, Center and (R.Width <= (ClientWidth - Header.Columns.GetVisibleFixedWidth)), Node); + end; + + Result := ScrolledVertically or ScrolledHorizontally; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.ScaledPixels(pPixels: TDimension): TDimension; + + /// Returns the given pixels scaled to the current dpi assuming that we designed at 96dpi (100%) +begin + Result := MulDiv(pPixels, {$if CompilerVersion > 31}Self.FCurrentPPI{$else}Screen.PixelsPerInch{$ifend}, 96); +end; + +function TBaseVirtualTree.ScrollIntoView(Column: TColumnIndex; Center: Boolean; Node: PVirtualNode = nil): Boolean; + +// Scrolls the columns so that the given column is in the client area and returns True if the columns really have been +// scrolled (e.g. to avoid further updates) else returns False. + +var + ColumnLeft, + ColumnRight: TDimension; + NewOffset, + OldOffset: TDimension; + R: TRect; + +begin + Result := False; + + if FHeader.UseColumns and FHeader.Columns.IsValidColumn(Column) then begin + ColumnLeft := Header.Columns.Items[Column].Left; + ColumnRight := ColumnLeft + Header.Columns.Items[Column].Width; + end else if Assigned(Node) and (toCenterScrollIntoView in FOptions.SelectionOptions) then begin + Center := False; + R := GetDisplayRect(Node, NoColumn, not (toGridExtensions in FOptions.MiscOptions)); + ColumnLeft := R.Left; + ColumnRight := R.Right; + end else + Exit; + + OldOffset := FOffsetX; + NewOffset := FEffectiveOffsetX; + if not (FHeader.UseColumns and (coFixed in Header.Columns[Column].Options)) and (not Center) then + begin + if ColumnRight > ClientWidth then + NewOffset := FEffectiveOffsetX + Min(ColumnRight - ClientWidth, + - (Header.Columns.GetVisibleFixedWidth - ColumnLeft)) + else if ColumnLeft < Header.Columns.GetVisibleFixedWidth then + NewOffset := FEffectiveOffsetX - (Header.Columns.GetVisibleFixedWidth - ColumnLeft); + if NewOffset <> FEffectiveOffsetX then + begin + if UseRightToLeftAlignment then + SetOffsetX(-FRangeX + ClientWidth + NewOffset) + else + SetOffsetX(-NewOffset); + end; + end + else if Center then + begin + NewOffset := FEffectiveOffsetX + ColumnLeft - Divide(Header.Columns.GetVisibleFixedWidth, 2) - Divide(ClientWidth, 2) + Divide(ColumnRight - ColumnLeft, 2); + if NewOffset <> FEffectiveOffsetX then + begin + if UseRightToLeftAlignment then + SetOffsetX(-FRangeX + ClientWidth + NewOffset) + else + SetOffsetX(-NewOffset); + end; + end; + Result := OldOffset <> FOffsetX; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SelectAll(VisibleOnly: Boolean); + +// Select all nodes in the tree. +// If VisibleOnly is True then only visible nodes are selected. + +var + Run: PVirtualNode; + NextFunction: TGetNextNodeProc; +begin + if not FSelectionLocked and (toMultiSelect in FOptions.SelectionOptions) then + begin + ClearTempCache; + if VisibleOnly then + begin + Run := GetFirstVisible(nil, True); + NextFunction := GetNextVisible; + end + else + begin + Run := GetFirst; + NextFunction := GetNext; + end; + BeginUpdate(); // Improve performance, see issue #690 + try + while Assigned(Run) do + begin + if not(vsSelected in Run.States) then + InternalCacheNode(Run); + Run := NextFunction(Run); + end;//while + if FTempNodeCount > 0 then + AddToSelection(FTempNodeCache, FTempNodeCount); + ClearTempCache; + finally + EndUpdate(); + end;//try..finally + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.Sort(Node: PVirtualNode; Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); + +// Sorts the given node. The application is queried about how to sort via the OnCompareNodes event. +// Column is simply passed to the the compare function so the application can also sort in a particular column. +// In order to free the application from taking care about the sort direction the parameter Direction is used. +// This way the application can always sort in increasing order, while this method reorders nodes according to this flag. + + //--------------- local functions ------------------------------------------- + + function MergeAscending(A, B: PVirtualNode): PVirtualNode; + + // Merges A and B (which both must be sorted via Compare) into one list. + + var + Dummy: TVirtualNode; + CompareResult: Integer; + begin + // This avoids checking for Result = nil in the loops. + Result := @Dummy; + while Assigned(A) and Assigned(B) do + begin + if OperationCanceled then + CompareResult := 0 + else + CompareResult := DoCompare(A, B, Column); + + if CompareResult <= 0 then + begin + Result.SetNextSibling(A); + Result := A; + A := A.NextSibling; + end + else + begin + Result.SetNextSibling(B); + Result := B; + B := B.NextSibling; + end; + end; + + // Just append the list which is not nil (or set end of result list to nil if both lists are nil). + if Assigned(A) then + Result.SetNextSibling(A) + else + Result.SetNextSibling(B); + // return start of the new merged list + Result := Dummy.NextSibling; + end; + + //--------------------------------------------------------------------------- + + function MergeDescending(A, B: PVirtualNode): PVirtualNode; + + // Merges A and B (which both must be sorted via Compare) into one list. + + var + Dummy: TVirtualNode; + CompareResult: Integer; + + begin + // this avoids checking for Result = nil in the loops + Result := @Dummy; + while Assigned(A) and Assigned(B) do + begin + if OperationCanceled then + CompareResult := 0 + else + CompareResult := DoCompare(A, B, Column); + + if CompareResult >= 0 then + begin + Result.SetNextSibling(A); + Result := A; + A := A.NextSibling; + end + else + begin + Result.SetNextSibling(B); + Result := B; + B := B.NextSibling; + end; + end; + + // Just append the list which is not nil (or set end of result list to nil if both lists are nil). + if Assigned(A) then + Result.SetNextSibling(A) + else + Result.SetNextSibling(B); + // Return start of the newly merged list. + Result := Dummy.NextSibling; + end; + + //--------------------------------------------------------------------------- + + function MergeSortAscending(var Node: PVirtualNode; N: Cardinal): PVirtualNode; + + // Sorts the list of nodes given by Node (which must not be nil). + + var + A, B: PVirtualNode; + + begin + if N > 1 then + begin + A := MergeSortAscending(Node, N div 2); + B := MergeSortAscending(Node, (N + 1) div 2); + Result := MergeAscending(A, B); + end + else + begin + Result := Node; + Node := Node.NextSibling; + Result.SetNextSibling(nil); + end; + end; + + //--------------------------------------------------------------------------- + + function MergeSortDescending(var Node: PVirtualNode; N: Cardinal): PVirtualNode; + + // Sorts the list of nodes given by Node (which must not be nil). + + var + A, B: PVirtualNode; + + begin + if N > 1 then + begin + A := MergeSortDescending(Node, N div 2); + B := MergeSortDescending(Node, (N + 1) div 2); + Result := MergeDescending(A, B); + end + else + begin + Result := Node; + Node := Node.NextSibling; + Result.SetNextSibling(nil); + end; + end; + + //--------------- end local functions --------------------------------------- + +var + Run: PVirtualNode; + Index: Cardinal; + +begin + InterruptValidation; + if tsEditPending in FStates then + begin + StopTimer(EditTimer); + DoStateChange([], [tsEditPending]); + end; + + if not (tsEditing in FStates) or DoEndEdit then + begin + if Node = nil then + Node := FRoot; + if vsHasChildren in Node.States then + begin + if (Node.ChildCount = 0) and DoInit then + InitChildren(Node); + // Make sure the children are valid, so they can be sorted at all. + if DoInit and (Node.ChildCount > 0) then + ValidateChildren(Node, False); + // Child count might have changed. + if Node.ChildCount > 1 then + begin + StartOperation(okSortNode); + try + // Sort the linked list, check direction flag only once. + if Direction = sdAscending then + Node.SetFirstChild(MergeSortAscending(Node.FirstChild, Node.ChildCount)) + else + Node.SetFirstChild(MergeSortDescending(Node.FirstChild, Node.ChildCount)); + finally + EndOperation(okSortNode); + end; + // Consolidate the child list finally. + Run := Node.FirstChild; + Run.SetPrevSibling(nil); + Index := 0; + repeat + Run.SetIndex(Index); + System.Inc(Index); + if Run.NextSibling = nil then + Break; + Run.NextSibling.SetPrevSibling(Run); + Run := Run.NextSibling; + until False; + Node.SetLastChild(Run); + + InvalidateCache; + end; + if FUpdateCount = 0 then + begin + ValidateCache; + Invalidate; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.SortTree(Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); + + //--------------- local function -------------------------------------------- + + procedure DoSort(Node: PVirtualNode); + + // Recursively sorts Node and its child nodes. + + var + Run: PVirtualNode; + + begin + Sort(Node, Column, Direction, DoInit); + // Recurse to next level + Run := Node.FirstChild; + while Assigned(Run) and not FOperationCanceled do + begin + if DoInit and not (vsInitialized in Run.States) then + InitNode(Run); + if (vsInitialized in Run.States) and (not (toAutoSort in TreeOptions.AutoOptions) or Expanded[Run]) then // There is no need to sort collapsed branches + DoSort(Run); + Run := Run.NextSibling; + end; + end; + + //--------------- end local function ---------------------------------------- + +begin + if RootNode.TotalCount <= 2 then + Exit;//Nothing to do if there are one or zero nodes. RootNode.TotalCount is 1 if there are no nodes in the treee as the root node counts too here. + + if not Assigned(FRoot.FirstChild) then + Exit; // Sorting should not initialize the root nodes + + // Instead of wrapping the sort using BeginUpdate/EndUpdate simply the update counter + // is modified. Otherwise the EndUpdate call will recurse here. + System.Inc(FUpdateCount); + try + if Column > InvalidColumn then + begin + StartOperation(okSortTree); + try + DoSort(FRoot); + finally + EndOperation(okSortTree); + end; + end; + InvalidateCache; + finally + if FUpdateCount > 0 then + System.Dec(FUpdateCount); + if FUpdateCount = 0 then + begin + ValidateCache; + Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ToggleNode(Node: PVirtualNode); + +// Changes a node's expand state to the opposite state. + +var + Child, + FirstVisible: PVirtualNode; + HeightDelta, + StepsR1, + StepsR2: TDimension; + Steps: Integer; + TogglingTree, + ChildrenInView, + NeedFullInvalidate, + NeedUpdate, + NodeInView, + PosHoldable, + TotalFit: Boolean; + ToggleData: TToggleAnimationData; + + //--------------- local function -------------------------------------------- + + procedure PrepareAnimation; + + // Prepares ToggleData. + + var + R: TRect; + S: TDimension; + M: TToggleAnimationMode; + + begin + with ToggleData do + begin + Window := Handle; + DC := TControlCanvas.Create; + DC.Control := Self; + + if (toShowBackground in FOptions.PaintOptions) and Assigned(FBackground.Graphic) then + Self.Brush.Style := bsClear + else + begin + Self.Brush.Style := bsSolid; + Self.Brush.Color := FColors.BackGroundColor; + end; + + Brush := Self.Brush; + + if (Mode1 <> tamNoScroll) and (Mode2 <> tamNoScroll) then + begin + if StepsR1 < StepsR2 then + begin + // As the primary rectangle is always R1 we will get a much smoother + // animation if R1 is the one that will be scrolled more. + R := R2; + R2 := R1; + R1 := R; + + M := Mode2; + Mode2 := Mode1; + Mode1 := M; + + S := StepsR2; + StepsR2 := StepsR1; + StepsR1 := S; + end; + ScaleFactor := StepsR2 / StepsR1; + MissedSteps := 0; + end; + + if Mode1 <> tamNoScroll then + Steps := StepsR1 + else + Steps := StepsR2; + end; + end; + + //--------------- end local function ---------------------------------------- + +begin + Assert(Assigned(Node), 'Node must not be nil.'); + + TogglingTree := tsToggling in FStates; + ChildrenInView := False; + HeightDelta := 0; + NeedFullInvalidate := False; + NeedUpdate := False; + NodeInView := False; + PosHoldable := False; + TotalFit := False; + + // We don't need to switch the expand state if the node is being deleted otherwise some + // updates (e.g. visible node count) are done twice with disasterous results). + if [vsDeleting, vsToggling] * Node.States = [] then + begin + try + DoStateChange([tsToggling]); + Include(Node.States, vsToggling); + + if vsExpanded in Node.States then + begin + if DoCollapsing(Node) then + begin + NeedUpdate := True; + + // Calculate the height delta right now as we need it for toChildrenAbove anyway. + HeightDelta := -Node.TotalHeight + NodeHeight[Node]; + if (FUpdateCount = 0) and (toAnimatedToggle in FOptions.AnimationOptions) and not + (tsCollapsing in FStates) then + begin + if tsHint in Self.FStates then + Application.CancelHint; + UpdateWindow(); + + // animated collapsing + with ToggleData do + begin + // Determine the animation behaviour and rectangle. If toChildrenAbove is set, the behaviour is depending + // on the position of the node to be collapsed. + R1 := GetDisplayRect(Node, NoColumn, False); + Mode2 := tamNoScroll; + if toChildrenAbove in FOptions.PaintOptions then + begin + PosHoldable := (FOffsetY + (Node.TotalHeight - NodeHeight[Node])) <= 0; + NodeInView := R1.Top < ClientHeight; + + StepsR1 := 0; + if NodeInView then + begin + if PosHoldable or not (toAdvancedAnimatedToggle in FOptions.AnimationOptions) then + begin + // Scroll the child nodes down. + Mode1 := tamScrollDown; + R1.Bottom := R1.Top; + R1.Top := 0; + StepsR1 := Min(R1.Bottom - R1.Top + 1, Node.TotalHeight - NodeHeight[Node]); + end + else + begin + // The position cannot be kept. So scroll the node up to its future position. + Mode1 := tamScrollUp; + R1.Top := Max(0, R1.Top + HeightDelta); + R1.Bottom := ClientHeight; + StepsR1 := FOffsetY - HeightDelta; + end; + end; + end + else + begin + if (FRangeY + FOffsetY - R1.Bottom + HeightDelta >= ClientHeight - R1.Bottom) or + (FRangeY <= ClientHeight) or (FOffsetY = 0) or not + (toAdvancedAnimatedToggle in FOptions.AnimationOptions) then + begin + // Do a simple scroll up over the child nodes. + Mode1 := tamScrollUp; + Inc(R1.Top, NodeHeight[Node]); + R1.Bottom := ClientHeight; + StepsR1 := Min(R1.Bottom - R1.Top + 1, -HeightDelta); + end + else + begin + // Scroll the node down to its future position. As FOffsetY will change we need to invalidate the + // whole tree. + Mode1 := tamScrollDown; + StepsR1 := Min(-FOffsetY, ClientHeight - FRangeY -FOffsetY - HeightDelta); + R1.Top := 0; + R1.Bottom := Min(ClientHeight, R1.Bottom + Steps); + NeedFullInvalidate := True; + end; + end; + + // No animation necessary if the node is below the current client height. + if R1.Top < ClientHeight then + begin + PrepareAnimation; + try + Animate(Steps, FAnimationDuration, ToggleCallback, @ToggleData); + finally + DC.Free; + end; + end; + end; + end; + + // collapse the node + AdjustTotalHeight(Node, IfThen(IsEffectivelyFiltered[Node], 0, NodeHeight[Node])); + if FullyVisible[Node] then + System.Dec(FVisibleCount, CountVisibleChildren(Node)); + Exclude(Node.States, vsExpanded); + DoCollapsed(Node); + + // Remove child nodes now, if enabled. + if (toAutoFreeOnCollapse in FOptions.AutoOptions) and (Node.ChildCount > 0) then + begin + DeleteChildren(Node); + Include(Node.States, vsHasChildren); + end; + end; + end + else + if DoExpanding(Node) then + begin + NeedUpdate := True; + // expand the node, need to adjust the height + if not (vsInitialized in Node.States) then + InitNode(Node); + if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then + InitChildren(Node); + + // Avoid setting the vsExpanded style if there are no child nodes. + if Node.ChildCount > 0 then + begin + // Iterate through the child nodes without initializing them. We have to determine the entire height. + Child := Node.FirstChild; + repeat + if vsVisible in Child.States then + begin + // Ensure the item height is measured + MeasureItemHeight(Canvas, Child); + + Inc(HeightDelta, Child.TotalHeight); + end; + Child := Child.NextSibling; + until Child = nil; + + // Getting the display rectangle is already done here as it is needed for toChildrenAbove in any case. + if (toChildrenAbove in FOptions.PaintOptions) or (FUpdateCount = 0) then + begin + with ToggleData do + begin + R1 := GetDisplayRect(Node, NoColumn, False); + Mode2 := tamNoScroll; + TotalFit := HeightDelta + NodeHeight[Node] <= ClientHeight; + + if toChildrenAbove in FOptions.PaintOptions then + begin + // The main goal with toChildrenAbove being set is to keep the nodes visual position so the user does + // not get confused. Therefore we need to scroll the view when the expanding is done. + PosHoldable := TotalFit and (FRangeY - ClientHeight >= 0) ; + ChildrenInView := (R1.Top - HeightDelta) >= 0; + NodeInView := R1.Bottom <= ClientHeight; + end + else + begin + PosHoldable := TotalFit; + ChildrenInView := R1.Bottom + HeightDelta <= ClientHeight; + end; + + R1.Bottom := ClientHeight; + end; + end; + + if FUpdateCount = 0 then + begin + // Do animated expanding if enabled. + if (ToggleData.R1.Top < ClientHeight) and ([tsPainting, tsExpanding] * FStates = []) and + (toAnimatedToggle in FOptions.AnimationOptions)then + begin + if tsHint in Self.FStates then + Application.CancelHint; + UpdateWindow(); + + // animated expanding + with ToggleData do + begin + if toChildrenAbove in FOptions.PaintOptions then + begin + // At first check if we hold the position, which is the most common case. + if not (toAdvancedAnimatedToggle in FOptions.AnimationOptions) or + (PosHoldable and ( (NodeInView and ChildrenInView) or not + (toAutoScrollOnExpand in FOptions.AutoOptions) )) then + begin + Mode1 := tamScrollUp; + R1 := Rect(R1.Left, 0, R1.Right, R1.Top); + StepsR1 := Min(HeightDelta, R1.Bottom); + end + else + begin + // If we will not hold the node's visual position we mostly scroll in both directions. + Mode1 := tamScrollDown; + Mode2 := tamScrollUp; + R2 := Rect(R1.Left, 0, R1.Right, R1.Top); + if not (toAutoScrollOnExpand in FOptions.AutoOptions) then + begin + // If we shall not or cannot scroll to the desired extent we calculate the new position (with + // max FOffsetY applied) and animate it that way. + StepsR1 := -FOffsetY - Max(FRangeY + HeightDelta - ClientHeight, 0) + HeightDelta; + if (FRangeY + HeightDelta - ClientHeight) <= 0 then + Mode2 := tamNoScroll + else + StepsR2 := Min(FRangeY + HeightDelta - ClientHeight, R2.Bottom); + end + else + begin + if TotalFit and NodeInView and (FRangeY + HeightDelta > ClientHeight) then + begin + // If the whole subtree will fit into the client area and the node is currently fully visible, + // the first child will be made the top node if possible. + if HeightDelta >= R1.Top then + StepsR1 := Abs(R1.Top - HeightDelta) + else + StepsR1 := ClientHeight - FRangeY; + end + else + if FRangeY + HeightDelta <= ClientHeight then + begin + // We cannot make the first child the top node as we cannot scroll to that extent, + // so we do a simple scroll down. + Mode2 := tamNoScroll; + StepsR1 := HeightDelta; + end + else + // If the subtree does not fit into the client area at once, the expanded node will + // be made the bottom node. + StepsR1 := ClientHeight - R1.Top - NodeHeight[Node]; + + if Mode2 <> tamNoScroll then + begin + if StepsR1 > 0 then + StepsR2 := Min(R1.Top, HeightDelta - StepsR1) + else + begin + // If the node is already at the bottom scrolling is needed. + Mode1 := tamNoScroll; + StepsR2 := Min(HeightDelta, R1.Bottom); + end; + end; + end; + end; + end + else + begin + // toChildrenAbove is not set. + if (PosHoldable and ChildrenInView) or not (toAutoScrollOnExpand in FOptions.AutoOptions) or not + (toAdvancedAnimatedToggle in FOptions.AnimationOptions) or (R1.Top <= 0) then + begin + // If the node will stay at its visual position, do a simple down-scroll. + Mode1 := tamScrollDown; + Inc(R1.Top, NodeHeight[Node]); + StepsR1 := Min(R1.Bottom - R1.Top, HeightDelta); + end + else + begin + // We will not hold the nodes visual position so perform a double scroll. + Mode1 := tamScrollUp; + Mode2 := tamScrollDown; + + R1.Bottom := R1.Top + NodeHeight[Node] + 1; + R1.Top := 0; + R2 := Rect(R1.Left, R1.Bottom, R1.Right, ClientHeight); + + StepsR1 := Min(HeightDelta - (ClientHeight - R2.Top), R1.Bottom - NodeHeight[Node]); + StepsR2 := ClientHeight - R2.Top; + end; + end; + + if ClientHeight >= R1.Top then + begin + PrepareAnimation; + try + Animate(Steps, FAnimationDuration, ToggleCallback, @ToggleData); + finally + DC.Free; + end; + end; + end; + end; + if toAutoSort in FOptions.AutoOptions then + Sort(Node, FHeader.SortColumn, FHeader.SortDirection, False); + end;// if UpdateCount = 0 + + Include(Node.States, vsExpanded); + AdjustTotalHeight(Node, HeightDelta, True); + if FullyVisible[Node] then + System.Inc(FVisibleCount, CountVisibleChildren(Node)); + + DoExpanded(Node); + end; + end; + + if NeedUpdate then + begin + InvalidateCache; + if FUpdateCount = 0 then + begin + ValidateCache; + if Node.ChildCount > 0 then + begin + UpdateRanges; + UpdateScrollBars(True); + if [tsPainting, tsExpanding] * FStates = [] then + begin + if (vsExpanded in Node.States) and ((toAutoScrollOnExpand in FOptions.AutoOptions) or + (toChildrenAbove in FOptions.PaintOptions)) then + begin + if toChildrenAbove in FOptions.PaintOptions then + begin + NeedFullInvalidate := True; + if (PosHoldable and ChildrenInView and NodeInView) or not + (toAutoScrollOnExpand in FOptions.AutoOptions) then + SetOffsetY(FOffsetY - HeightDelta) + else + if TotalFit and NodeInView then + begin + FirstVisible := GetFirstVisible(Node, True); + if Assigned(FirstVisible) then // otherwise there is no visible child at all + SetOffsetY(FOffsetY - GetDisplayRect(FirstVisible, NoColumn, False).Top); + end + else + BottomNode := Node; + end + else + begin + // Scroll as much child nodes into view as possible if the node has been expanded. + if PosHoldable then + NeedFullInvalidate := ScrollIntoView(GetLastVisible(Node, True), False) + else + begin + TopNode := Node; + NeedFullInvalidate := True; + end; + end; + end + else + begin + // If we have collapsed the node or toAutoScrollOnExpand is not set, we try to keep the nodes + // visual position. + if toChildrenAbove in FOptions.PaintOptions then + SetOffsetY(FOffsetY - HeightDelta); + NeedFullInvalidate := True; + end; + end; + + //UpdateScrollBars(True); Moved up + + // Check for automatically scrolled tree. + if NeedFullInvalidate then + Invalidate + else + InvalidateToBottom(Node); + end + else + InvalidateNode(Node); + end + else + begin + UpdateRanges; + UpdateScrollBars(True); + end; + end; + + finally + Exclude(Node.States, vsToggling); + if not TogglingTree then + DoStateChange([], [tsToggling]); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateHorizontalRange; + +begin + if FHeader.UseColumns then + SetRangeX(FHeader.Columns.TotalWidth) + else + SetRangeX(GetMaxRightExtend); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateHorizontalScrollBar(DoRepaint: Boolean); + +var + ScrollInfo: TScrollInfo; + +begin + UpdateHorizontalRange; + + if IsUpdating or not HandleAllocated then + Exit; + + // Adjust effect scroll offset depending on bidi mode. + if UseRightToLeftAlignment then + FEffectiveOffsetX := FRangeX - ClientWidth + FOffsetX + else + FEffectiveOffsetX := -FOffsetX; + + if FScrollBarOptions.ScrollBars in [System.UITypes.TScrollStyle.ssHorizontal, System.UITypes.TScrollStyle.ssBoth] then + begin + ZeroMemory (@ScrollInfo, SizeOf(ScrollInfo)); + ScrollInfo.cbSize := SizeOf(ScrollInfo); + ScrollInfo.fMask := SIF_ALL; + GetScrollInfo(SB_HORZ, ScrollInfo); + + if (FRangeX > ClientWidth) or FScrollBarOptions.AlwaysVisible then + begin + DoShowScrollBar(SB_HORZ, True); + + ScrollInfo.nMin := 0; + ScrollInfo.nMax := FRangeX; + ScrollInfo.nPos := FEffectiveOffsetX; + ScrollInfo.nPage := Max(0, ClientWidth + 1); + + ScrollInfo.fMask := SIF_ALL or ScrollMasks[FScrollBarOptions.AlwaysVisible]; + SetScrollInfo(SB_HORZ, ScrollInfo, DoRepaint); // 1 app freeze seen here in TreeSize 8.1.0 after ScaleForPpi() + if DoRepaint then + RedrawWindow(nil, 0, RDW_FRAME or RDW_INVALIDATE); // Fixes issue #698 + end + else + begin + ScrollInfo.nMin := 0; + ScrollInfo.nMax := 0; + ScrollInfo.nPos := 0; + ScrollInfo.nPage := 0; + DoShowScrollBar(SB_HORZ, False); + SetScrollInfo(SB_HORZ, ScrollInfo, False); + end; + + // Since the position is automatically changed if it doesn't meet the range + // we better read the current position back to stay synchronized. + FEffectiveOffsetX := GetScrollPos(SB_HORZ); + if UseRightToLeftAlignment then + SetOffsetX(-FRangeX + ClientWidth + FEffectiveOffsetX) + else + SetOffsetX(-FEffectiveOffsetX); + end + else + begin + DoShowScrollBar(SB_HORZ, False); + + // Reset the current horizontal offset to account for window resize etc. + SetOffsetX(FOffsetX); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateRanges; + +begin + UpdateVerticalRange; + UpdateHorizontalRange; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateScrollBars(DoRepaint: Boolean); + +// adjusts scrollbars to reflect current size and paint offset of the tree + +begin + if HandleAllocated then + begin + UpdateVerticalScrollBar(DoRepaint); + UpdateHorizontalScrollBar(DoRepaint); + Perform(CM_UPDATE_VCLSTYLE_SCROLLBARS,0,0); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateStyleElements; +begin + inherited; + UpdateHeaderRect; + FHeader.Columns.PaintHeader(Canvas, FHeaderRect, Point(0,0)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.UpdateVerticalRange; + +begin + // Total node height includes the height of the invisible root node. + FRangeY := FRoot.TotalHeight - FRoot.NodeHeight + FBottomSpace; +end; + +//---------------------------------------------------------------------------------------------------------------------- + + procedure TBaseVirtualTree.UpdateVerticalScrollBar(DoRepaint: Boolean); + +var + ScrollInfo: TScrollInfo; + +begin + UpdateVerticalRange; + + if (IsUpdating) then + Exit; + Assert(GetCurrentThreadId = MainThreadId, 'UI controls like ' + Classname + ' and its scrollbars should only be manipulated through the main thread.'); + + if FScrollBarOptions.ScrollBars in [TScrollStyle.ssVertical, TScrollStyle.ssBoth] then + begin + ScrollInfo.cbSize := SizeOf(ScrollInfo); + ScrollInfo.fMask := SIF_ALL; + GetScrollInfo(SB_VERT, ScrollInfo); + + if (FRangeY > ClientHeight) or FScrollBarOptions.AlwaysVisible then + begin + DoShowScrollBar(SB_VERT, True); + + ScrollInfo.nMin := 0; + ScrollInfo.nMax := IfThen(FRangeY < MaxInt, FRangeY, MaxInt); // TScrollInfo values are signed 32bit only + ScrollInfo.nPos := -FOffsetY; + ScrollInfo.nPage := Max(0, ClientHeight + 1); + + ScrollInfo.fMask := SIF_ALL or ScrollMasks[FScrollBarOptions.AlwaysVisible]; + SetScrollInfo(SB_VERT, ScrollInfo, DoRepaint); + end + else + begin + ScrollInfo.nMin := 0; + ScrollInfo.nMax := 0; + ScrollInfo.nPos := 0; + ScrollInfo.nPage := 0; + DoShowScrollBar(SB_VERT, False); + SetScrollInfo(SB_VERT, ScrollInfo, False); + end; + + // Since the position is automatically changed if it doesn't meet the range + // we better read the current position back to stay synchronized. + SetOffsetY(-GetScrollPos(SB_VERT)); + end + else + begin + DoShowScrollBar(SB_VERT, False); + + // Reset the current vertical offset to account for window resize etc. + SetOffsetY(FOffsetY); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseVirtualTree.UseRightToLeftReading: Boolean; + +// The tree can handle right-to-left reading also on non-middle-east systems, so we cannot use the same function as +// it is implemented in TControl. + +begin + Result := BiDiMode <> bdLeftToRight; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ValidateChildren(Node: PVirtualNode; Recursive: Boolean); + +// Ensures that the children of the given node (and all their children, if Recursive is True) are initialized. +// Node must already be initialized + +var + Child: PVirtualNode; + +begin + if Node = nil then + Node := FRoot; + + if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then + InitChildren(Node); + Child := Node.FirstChild; + while Assigned(Child) do + begin + ValidateNode(Child, Recursive); + Child := Child.NextSibling; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseVirtualTree.ValidateNode(Node: PVirtualNode; Recursive: Boolean); + +// Ensures that the given node (and all its children, if Recursive is True) are initialized. + +var + Child: PVirtualNode; + +begin + if Node = nil then + Node := FRoot + else + if not (vsInitialized in Node.States) then + InitNode(Node); + + if Recursive then + begin + if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then + InitChildren(Node); + Child := Node.FirstChild; + while Assigned(Child) do + begin + ValidateNode(Child, Recursive); + Child := Child.NextSibling; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +initialization + +finalization + FinalizeGlobalStructures(); + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.Classes.pas b/components/virtualtreeview/Source/VirtualTrees.Classes.pas index 444c536ca..856f28c4f 100644 --- a/components/virtualtreeview/Source/VirtualTrees.Classes.pas +++ b/components/virtualtreeview/Source/VirtualTrees.Classes.pas @@ -1,4 +1,4 @@ -unit VirtualTrees.Classes; +unit VirtualTrees.Classes; // The contents of this file are subject to the Mozilla Public License // Version 1.1 (the "License"); you may not use this file except in compliance @@ -108,8 +108,8 @@ procedure TBufferedRawByteString.Add(const S: RawByteString); FPosition := FStart + LastOffset; FEnd := FStart + NewLen; end; - Move(PAnsiChar(S)^, FPosition^, Len); - Inc(FPosition, Len); + System.Move(PAnsiChar(S)^, FPosition^, Len); + System.Inc(FPosition, Len); end; //---------------------------------------------------------------------------------------------------------------------- @@ -133,9 +133,9 @@ procedure TBufferedRawByteString.AddNewLine; FEnd := FStart + NewLen; end; FPosition^ := #13; - Inc(FPosition); + System.Inc(FPosition); FPosition^ := #10; - Inc(FPosition); + System.Inc(FPosition); end; //----------------- TBufferedString -------------------------------------------------------------------------------- @@ -179,8 +179,8 @@ procedure TBufferedString.Add(const S: string); FPosition := FStart + LastOffset; FEnd := FStart + NewLen; end; - Move(PWideChar(S)^, FPosition^, 2 * Len); - Inc(FPosition, Len); + System.Move(PWideChar(S)^, FPosition^, 2 * Len); + System.Inc(FPosition, Len); end; //---------------------------------------------------------------------------------------------------------------------- @@ -204,9 +204,9 @@ procedure TBufferedString.AddNewLine; FEnd := FStart + NewLen; end; FPosition^ := #13; - Inc(FPosition); + System.Inc(FPosition); FPosition^ := #10; - Inc(FPosition); + System.Inc(FPosition); end; diff --git a/components/virtualtreeview/Source/VirtualTrees.ClipBoard.pas b/components/virtualtreeview/Source/VirtualTrees.ClipBoard.pas index e995a8775..017a73625 100644 --- a/components/virtualtreeview/Source/VirtualTrees.ClipBoard.pas +++ b/components/virtualtreeview/Source/VirtualTrees.ClipBoard.pas @@ -32,7 +32,7 @@ interface Winapi.Windows, Winapi.ActiveX, System.Classes, - VirtualTrees; + VirtualTrees.BaseTree; type TClipboardFormatEntry = record @@ -98,6 +98,17 @@ TClipboardFormatList = class class function FindFormat(Fmt: Word; var Description: string): TVirtualTreeClass; overload; end; +var + // Clipboard format IDs used in OLE drag'n drop and clipboard transfers. + CF_VIRTUALTREE, + CF_VTREFERENCE, // Reference to a virtual tree + CF_VTHEADERREFERENCE, // A drag and drop of the column header took place + CF_VRTF, + CF_VRTFNOOBJS, // Unfortunately CF_RTF* is already defined as being + // registration strings so I have to use different identifiers. + CF_HTML, + CF_CSV: Word; + implementation @@ -405,3 +416,4 @@ finalization FreeAndNil(_List); end. + diff --git a/components/virtualtreeview/Source/VirtualTrees.Colors.pas b/components/virtualtreeview/Source/VirtualTrees.Colors.pas new file mode 100644 index 000000000..78c1c079e --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.Colors.pas @@ -0,0 +1,274 @@ +unit VirtualTrees.Colors; + +interface + +uses + System.Classes, + Vcl.Graphics, + Vcl.Themes, + Vcl.Controls; + +type + //class to collect all switchable colors into one place + TVTColors = class(TPersistent) + private type + TVTColorEnum = ( + cDisabledColor + , cDropMarkColor + , cDropTargetColor + , cFocusedSelectionColor + , cGridLineColor + , cTreeLineColor + , cUnfocusedSelectionColor + , cBorderColor + , cHotColor + , cFocusedSelectionBorderColor + , cUnfocusedSelectionBorderColor + , cDropTargetBorderColor + , cSelectionRectangleBlendColor + , cSelectionRectangleBorderColor + , cHeaderHotColor + , cSelectionTextColor + , cUnfocusedColor + ); + + //Please make sure that the published Color properties at the corresponding index + //have the same color if you change anything here! + const + cDefaultColors : array [TVTColorEnum] of TColor = ( + clBtnShadow, //DisabledColor + clHighlight, //DropMarkColor + clHighlight, //DropTargetColor + clHighlight, //FocusedSelectionColor + clBtnFace, //GridLineColor + clBtnShadow, //TreeLineColor + clInactiveCaption, //UnfocusedSelectionColor + clBtnFace, //BorderColor + clWindowText, //HotColor + clHighlight, //FocusedSelectionBorderColor + clInactiveCaption, //UnfocusedSelectionBorderColor + clHighlight, //DropTargetBorderColor + clHighlight, //SelectionRectangleBlendColor + clHighlight, //SelectionRectangleBorderColor + clBtnShadow, //HeaderHotColor + clHighlightText, //SelectionTextColor + clInactiveCaptionText //UnfocusedColor [IPK] + ); + private + FOwner : TCustomControl; + FColors : array [TVTColorEnum] of TColor; //[IPK] 15 -> 16 + function GetColor(const Index : TVTColorEnum) : TColor; + procedure SetColor(const Index : TVTColorEnum; const Value : TColor); + function GetBackgroundColor : TColor; + function GetHeaderFontColor : TColor; + function GetNodeFontColor : TColor; + public + constructor Create(AOwner : TCustomControl); + + procedure Assign(Source : TPersistent); override; + function GetSelectedNodeFontColor(Focused : boolean) : TColor; + property BackGroundColor : TColor read GetBackgroundColor; + property HeaderFontColor : TColor read GetHeaderFontColor; + property NodeFontColor : TColor read GetNodeFontColor; + //Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) + function StyleServices(AControl : TControl = nil) : TCustomStyleServices; + published + property BorderColor : TColor index cBorderColor read GetColor write SetColor default clBtnFace; + property DisabledColor : TColor index cDisabledColor read GetColor write SetColor default clBtnShadow; + property DropMarkColor : TColor index cDropMarkColor read GetColor write SetColor default clHighlight; + property DropTargetColor : TColor index cDropTargetColor read GetColor write SetColor default clHighlight; + property DropTargetBorderColor : TColor index cDropTargetBorderColor read GetColor write SetColor default clHighlight; + ///The background color of selected nodes in case the tree has the focus, or the toPopupMode flag is set. + property FocusedSelectionColor : TColor index cFocusedSelectionColor read GetColor write SetColor default clHighlight; + ///The border color of selected nodes when the tree has the focus. + property FocusedSelectionBorderColor : TColor index cFocusedSelectionBorderColor read GetColor write SetColor default clHighlight; + ///The color of the grid lines + property GridLineColor : TColor index cGridLineColor read GetColor write SetColor default clBtnFace; + property HeaderHotColor : TColor index cHeaderHotColor read GetColor write SetColor default clBtnShadow; + property HotColor : TColor index cHotColor read GetColor write SetColor default clWindowText; + property SelectionRectangleBlendColor : TColor index cSelectionRectangleBlendColor read GetColor write SetColor default clHighlight; + property SelectionRectangleBorderColor : TColor index cSelectionRectangleBorderColor read GetColor write SetColor default clHighlight; + ///The text color of selected nodes + property SelectionTextColor : TColor index cSelectionTextColor read GetColor write SetColor default clHighlightText; + property TreeLineColor : TColor index cTreeLineColor read GetColor write SetColor default clBtnShadow; + property UnfocusedColor : TColor index cUnfocusedColor read GetColor write SetColor default clInactiveCaptionText; //[IPK] Added + ///The background color of selected nodes in case the tree does not have the focus and the toPopupMode flag is not set. + property UnfocusedSelectionColor : TColor index cUnfocusedSelectionColor read GetColor write SetColor default clInactiveCaption; + ///The border color of selected nodes in case the tree does not have the focus and the toPopupMode flag is not set. + property UnfocusedSelectionBorderColor : TColor index cUnfocusedSelectionBorderColor read GetColor write SetColor default clInactiveCaption; + end; + +implementation + +uses + WinApi.Windows, + VirtualTrees.Types, + VirtualTrees.Utils, + VirtualTrees.StyleHooks, + VirtualTrees.BaseTree; + +type + TBaseVirtualTreeCracker = class(TBaseVirtualTree); + + TVTColorsHelper = class helper for TVTColors + function TreeView : TBaseVirtualTreeCracker; + end; + + //----------------- TVTColors ------------------------------------------------------------------------------------------ + +constructor TVTColors.Create(AOwner : TCustomControl); +var + CE : TVTColorEnum; +begin + FOwner := AOwner; + for CE := Low(TVTColorEnum) to High(TVTColorEnum) do + FColors[CE] := cDefaultColors[CE]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTColors.GetBackgroundColor : TColor; +begin + //XE2 VCL Style + if TreeView.VclStyleEnabled and (seClient in FOwner.StyleElements) then + Result := StyleServices.GetStyleColor(scTreeView) + else + Result := TreeView.Color; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTColors.GetColor(const Index : TVTColorEnum) : TColor; +begin + //Only try to fetch the color via StyleServices if theses are enabled + //Return default/user defined color otherwise + if not (csDesigning in TreeView.ComponentState) { see issue #1185 } and TreeView.VclStyleEnabled then + begin + //If the ElementDetails are not defined, fall back to the SystemColor + case Index of + cDisabledColor : + if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemDisabled), ecTextColor, Result) then + Result := StyleServices.GetSystemColor(FColors[Index]); + cTreeLineColor : + if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttBranch), ecBorderColor, Result) then + Result := StyleServices.GetSystemColor(FColors[Index]); + cBorderColor : + if (seBorder in FOwner.StyleElements) then + Result := StyleServices.GetSystemColor(FColors[Index]) + else + Result := FColors[Index]; + cHotColor : + if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemHot), ecTextColor, Result) then + Result := StyleServices.GetSystemColor(FColors[Index]); + cHeaderHotColor : + if not StyleServices.GetElementColor(StyleServices.GetElementDetails(thHeaderItemHot), ecTextColor, Result) then + Result := StyleServices.GetSystemColor(FColors[Index]); + cSelectionTextColor : + if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemSelected), ecTextColor, Result) then + Result := StyleServices.GetSystemColor(clHighlightText); + cUnfocusedColor : + if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemSelectedNotFocus), ecTextColor, Result) then + Result := StyleServices.GetSystemColor(FColors[Index]); + else + Result := StyleServices.GetSystemColor(FColors[Index]); + end; + end + else + Result := FColors[Index]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTColors.GetHeaderFontColor : TColor; +begin + //XE2+ VCL Style + if TreeView.VclStyleEnabled and (seFont in FOwner.StyleElements) then + StyleServices.GetElementColor(StyleServices.GetElementDetails(thHeaderItemNormal), ecTextColor, Result) + else + Result := TreeView.Header.Font.Color; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTColors.GetNodeFontColor : TColor; +begin + if TreeView.VclStyleEnabled and (seFont in FOwner.StyleElements) then + StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemNormal), ecTextColor, Result) + else + Result := TreeView.Font.Color; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTColors.GetSelectedNodeFontColor(Focused : boolean) : TColor; +begin + if Focused then + begin + if (tsUseExplorerTheme in TreeView.TreeStates) and not IsHighContrastEnabled then + begin + Result := NodeFontColor + end + else + Result := SelectionTextColor + end//if Focused + else + Result := UnfocusedColor; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTColors.SetColor(const Index : TVTColorEnum; const Value : TColor); +begin + if FColors[Index] <> Value then + begin + FColors[Index] := Value; + if not (csLoading in FOwner.ComponentState) and FOwner.HandleAllocated then + begin + //Cause helper bitmap rebuild if the button color changed. + case Index of + cTreeLineColor : + begin + TreeView.PrepareBitmaps(True, False); + FOwner.Invalidate; + end; + cBorderColor : + RedrawWindow(FOwner.Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN) + else + if not (tsPainting in TreeView.TreeStates) then // See issue #1186 + FOwner.Invalidate; + end;//case + end;// if + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTColors.StyleServices(AControl : TControl) : TCustomStyleServices; +begin + if AControl = nil then + AControl := FOwner; + Result := VTStyleServices(AControl); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTColors.Assign(Source : TPersistent); +begin + if Source is TVTColors then + begin + FColors := TVTColors(Source).FColors; + if TreeView.UpdateCount = 0 then + TreeView.Invalidate; + end + else + inherited; +end; + +{ TVTColorsHelper } + +function TVTColorsHelper.TreeView : TBaseVirtualTreeCracker; +begin + Result := TBaseVirtualTreeCracker(FOwner); +end; + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.DataObject.pas b/components/virtualtreeview/Source/VirtualTrees.DataObject.pas new file mode 100644 index 000000000..771b149d0 --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.DataObject.pas @@ -0,0 +1,508 @@ +unit VirtualTrees.DataObject; + +interface + +uses + WinApi.ActiveX, + WinApi.Windows, + System.Classes, + Vcl.Controls, + VirtualTrees.Types; + +type + IDataObject = WinApi.ActiveX.IDataObject; + + // IDataObject.SetData support + TInternalStgMedium = packed record + Format : TClipFormat; + Medium : TStgMedium; + end; + + TInternalStgMediumArray = array of TInternalStgMedium; + + // This data object is used in two different places. One is for clipboard operations and the other while dragging. + TVTDataObject = class(TInterfacedObject, IDataObject) + private + FOwner : TCustomControl; // The tree which provides clipboard or drag data. + FHeader : TPersistent; // The tree which provides clipboard or drag data. + FForClipboard : Boolean; // Determines which data to render with GetData. + FFormatEtcArray : TFormatEtcArray; + FInternalStgMediumArray : TInternalStgMediumArray; // The available formats in the DataObject + FAdviseHolder : IDataAdviseHolder; // Reference to an OLE supplied implementation for advising. + protected + function CanonicalIUnknown(const TestUnknown : IUnknown) : IUnknown; + function EqualFormatEtc(FormatEtc1, FormatEtc2 : TFormatEtc) : Boolean; + function FindFormatEtc(TestFormatEtc : TFormatEtc; const FormatEtcArray : TFormatEtcArray) : Integer; + function FindInternalStgMedium(Format : TClipFormat) : PStgMedium; + function HGlobalClone(HGlobal : THandle) : THandle; + function RenderInternalOLEData(const FormatEtcIn : TFormatEtc; var Medium : TStgMedium; var OLEResult : HResult) : Boolean; + function StgMediumIncRef(const InStgMedium : TStgMedium; var OutStgMedium : TStgMedium; CopyInMedium : Boolean; const DataObject : IDataObject) : HResult; + + property ForClipboard : Boolean read FForClipboard; + property FormatEtcArray : TFormatEtcArray read FFormatEtcArray write FFormatEtcArray; + property InternalStgMediumArray : TInternalStgMediumArray read FInternalStgMediumArray write FInternalStgMediumArray; + property Owner : TCustomControl read FOwner; + public + constructor Create(AOwner : TCustomControl; ForClipboard : Boolean); overload; + constructor Create(AHeader : TPersistent; AOwner : TCustomControl); overload; + destructor Destroy; override; + + function DAdvise(const FormatEtc : TFormatEtc; advf : Integer; const advSink : IAdviseSink; out dwConnection : Integer) : HResult; virtual; stdcall; + function DUnadvise(dwConnection : Integer) : HResult; virtual; stdcall; + function EnumDAdvise(out enumAdvise : IEnumStatData) : HResult; virtual; stdcall; + function EnumFormatEtc(Direction : Integer; out EnumFormatEtc : IEnumFormatEtc) : HResult; virtual; stdcall; + function GetCanonicalFormatEtc(const FormatEtc : TFormatEtc; out FormatEtcOut : TFormatEtc) : HResult; virtual; stdcall; + function GetData(const FormatEtcIn : TFormatEtc; out Medium : TStgMedium) : HResult; virtual; stdcall; + function GetDataHere(const FormatEtc : TFormatEtc; out Medium : TStgMedium) : HResult; virtual; stdcall; + function QueryGetData(const FormatEtc : TFormatEtc) : HResult; virtual; stdcall; + function SetData(const FormatEtc : TFormatEtc; var Medium : TStgMedium; DoRelease : BOOL) : HResult; virtual; stdcall; + end; + +implementation + +uses + VirtualTrees.ClipBoard, + VirtualTrees.DragnDrop, + VirtualTrees.BaseTree; + +type + TVTCracker = class(TBaseVirtualTree); + + //----------------- TVTDataObject -------------------------------------------------------------------------------------- + +constructor TVTDataObject.Create(AOwner : TCustomControl; ForClipboard : Boolean); +begin + inherited Create; + + FOwner := AOwner; + FForClipboard := ForClipboard; + if Assigned(FOWner) then + TVTCracker(FOwner).GetNativeClipboardFormats(FFormatEtcArray); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +constructor TVTDataObject.Create(AHeader: TPersistent; AOwner : TCustomControl); +begin + Create(AOwner, False); + FHeader := AHeader; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +destructor TVTDataObject.Destroy; +var + I : Integer; + StgMedium : PStgMedium; +begin + // Cancel a pending clipboard operation if this data object was created for the clipboard and + // is freed because something else is placed there. + if FForClipboard and not (tsClipboardFlushing in TBaseVirtualTree(FOwner).TreeStates) then + TBaseVirtualTree(FOwner).CancelCutOrCopy; + + // Release any internal clipboard formats + for I := 0 to High(FormatEtcArray) do + begin + StgMedium := FindInternalStgMedium(FormatEtcArray[I].cfFormat); + if Assigned(StgMedium) then + ReleaseStgMedium(StgMedium^); + end; + + FormatEtcArray := nil; + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.CanonicalIUnknown(const TestUnknown : IUnknown) : IUnknown; +// Uses COM object identity: An explicit call to the IUnknown::QueryInterface method, requesting the IUnknown +// interface, will always return the same pointer. +begin + if Assigned(TestUnknown) then + begin + if TestUnknown.QueryInterface(IUnknown, Result) = 0 then + Result._Release // Don't actually need it just need the pointer value + else + Result := TestUnknown; + end + else + Result := TestUnknown; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.EqualFormatEtc(FormatEtc1, FormatEtc2 : TFormatEtc) : Boolean; +begin + Result := (FormatEtc1.cfFormat = FormatEtc2.cfFormat) and (FormatEtc1.ptd = FormatEtc2.ptd) and (FormatEtc1.dwAspect = FormatEtc2.dwAspect) and + (FormatEtc1.lindex = FormatEtc2.lindex) and (FormatEtc1.tymed and FormatEtc2.tymed <> 0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.FindFormatEtc(TestFormatEtc : TFormatEtc; const FormatEtcArray : TFormatEtcArray) : Integer; +var + I : Integer; +begin + Result := - 1; + for I := 0 to High(FormatEtcArray) do + begin + if EqualFormatEtc(TestFormatEtc, FormatEtcArray[I]) then + begin + Result := I; + Break; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.FindInternalStgMedium(Format : TClipFormat) : PStgMedium; +var + I : Integer; +begin + Result := nil; + for I := 0 to High(InternalStgMediumArray) do + begin + if Format = InternalStgMediumArray[I].Format then + begin + Result := @InternalStgMediumArray[I].Medium; + Break; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.HGlobalClone(HGlobal : THandle) : THandle; +// Returns a global memory block that is a copy of the passed memory block. +var + Size : Cardinal; + Data, NewData : PByte; +begin + Size := GlobalSize(HGlobal); + Result := GlobalAlloc(GPTR, Size); + Data := GlobalLock(HGlobal); + try + NewData := GlobalLock(Result); + try + Move(Data^, NewData^, Size); + finally + GlobalUnLock(Result); + end; + finally + GlobalUnLock(HGlobal); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.RenderInternalOLEData(const FormatEtcIn : TFormatEtc; var Medium : TStgMedium; var OLEResult : HResult) : Boolean; +// Tries to render one of the formats which have been stored via the SetData method. +// Since this data is already there it is just copied or its reference count is increased (depending on storage medium). +var + InternalMedium : PStgMedium; +begin + Result := True; + InternalMedium := FindInternalStgMedium(FormatEtcIn.cfFormat); + if Assigned(InternalMedium) then + OLEResult := StgMediumIncRef(InternalMedium^, Medium, False, Self as IDataObject) + else + Result := False; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.StgMediumIncRef(const InStgMedium : TStgMedium; var OutStgMedium : TStgMedium; CopyInMedium : Boolean; const DataObject : IDataObject) : HResult; +// InStgMedium is the data that is requested, OutStgMedium is the data that we are to return either a copy of or +// increase the IDataObject's reference and send ourselves back as the data (unkForRelease). The InStgMedium is usually +// the result of a call to find a particular FormatEtc that has been stored locally through a call to SetData. +// If CopyInMedium is not true we already have a local copy of the data when the SetData function was called (during +// that call the CopyInMedium must be true). Then as the caller asks for the data through GetData we do not have to make +// copy of the data for the caller only to have them destroy it then need us to copy it again if necessary. +// This way we increase the reference count to ourselves and pass the STGMEDIUM structure initially stored in SetData. +// This way when the caller frees the structure it sees the unkForRelease is not nil and calls Release on the object +// instead of destroying the actual data. +var + Len : Integer; +begin + Result := S_OK; + + // Simply copy all fields to start with. + OutStgMedium := InStgMedium; + // The data handled here always results from a call of SetData we got. This ensures only one storage format + // is indicated and hence the case statement below is safe (IDataObject.GetData can optionally use several + // storage formats). + case InStgMedium.tymed of + TYMED_HGLOBAL : + begin + if CopyInMedium then + begin + // Generate a unique copy of the data passed + OutStgMedium.HGlobal := HGlobalClone(InStgMedium.HGlobal); + if OutStgMedium.HGlobal = 0 then + Result := E_OUTOFMEMORY; + end + else + // Don't generate a copy just use ourselves and the copy previously saved. + OutStgMedium.unkForRelease := Pointer(DataObject); // Does not increase RefCount. + end; + TYMED_FILE : + begin + Len := lstrLenW(InStgMedium.lpszFileName) + 1; // Don't forget the terminating null character. + OutStgMedium.lpszFileName := CoTaskMemAlloc(2 * Len); + Move(InStgMedium.lpszFileName^, OutStgMedium.lpszFileName^, 2 * Len); + end; + TYMED_ISTREAM : + IUnknown(OutStgMedium.stm)._AddRef; + TYMED_ISTORAGE : + IUnknown(OutStgMedium.stg)._AddRef; + TYMED_GDI : + if not CopyInMedium then + // Don't generate a copy just use ourselves and the previously saved data. + OutStgMedium.unkForRelease := Pointer(DataObject) // Does not increase RefCount. + else + Result := DV_E_TYMED; // Don't know how to copy GDI objects right now. + TYMED_MFPICT : + if not CopyInMedium then + // Don't generate a copy just use ourselves and the previously saved data. + OutStgMedium.unkForRelease := Pointer(DataObject) // Does not increase RefCount. + else + Result := DV_E_TYMED; // Don't know how to copy MetaFile objects right now. + TYMED_ENHMF : + if not CopyInMedium then + // Don't generate a copy just use ourselves and the previously saved data. + OutStgMedium.unkForRelease := Pointer(DataObject) // Does not increase RefCount. + else + Result := DV_E_TYMED; // Don't know how to copy enhanced metafiles objects right now. + else + Result := DV_E_TYMED; + end; + + if (Result = S_OK) and Assigned(OutStgMedium.unkForRelease) then + IUnknown(OutStgMedium.unkForRelease)._AddRef; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.DAdvise(const FormatEtc : TFormatEtc; advf : Integer; const advSink : IAdviseSink; out dwConnection : Integer) : HResult; +// Advise sink management is greatly simplified by the IDataAdviseHolder interface. +// We use this interface and forward all concerning calls to it. +begin + Result := S_OK; + if FAdviseHolder = nil then + Result := CreateDataAdviseHolder(FAdviseHolder); + if Result = S_OK then + Result := FAdviseHolder.Advise(Self as IDataObject, FormatEtc, advf, advSink, dwConnection); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.DUnadvise(dwConnection : Integer) : HResult; +begin + if FAdviseHolder = nil then + Result := E_NOTIMPL + else + Result := FAdviseHolder.Unadvise(dwConnection); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.EnumDAdvise(out enumAdvise : IEnumStatData) : HResult; +begin + if FAdviseHolder = nil then + Result := OLE_E_ADVISENOTSUPPORTED + else + Result := FAdviseHolder.enumAdvise(enumAdvise); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.EnumFormatEtc(Direction : Integer; out EnumFormatEtc : IEnumFormatEtc) : HResult; +var + NewList : TEnumFormatEtc; +begin + Result := E_FAIL; + if Direction = DATADIR_GET then + begin + NewList := TEnumFormatEtc.Create(FormatEtcArray); + EnumFormatEtc := NewList as IEnumFormatEtc; + Result := S_OK; + end + else + EnumFormatEtc := nil; + if EnumFormatEtc = nil then + Result := OLE_S_USEREG; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.GetCanonicalFormatEtc(const FormatEtc : TFormatEtc; out FormatEtcOut : TFormatEtc) : HResult; +begin + Result := DATA_S_SAMEFORMATETC; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.GetData(const FormatEtcIn : TFormatEtc; out Medium : TStgMedium) : HResult; +// Data is requested by clipboard or drop target. This method dispatchs the call +// depending on the data being requested. +var + I : Integer; + Data : PVTReference; +begin + // See if this is a header column drag and drop + if (FormatEtcIn.cfFormat = CF_VTHEADERREFERENCE) and Assigned(FHeader) then + begin + Medium.HGlobal := GlobalAlloc(GHND or GMEM_SHARE, SizeOf(TVTReference)); + Data := GlobalLock(Medium.HGlobal); + Data.Process := GetCurrentProcessID; + Data.Tree := TBaseVirtualTree(FOwner); + GlobalUnLock(Medium.HGlobal); + Medium.tymed := TYMED_HGLOBAL; + Medium.unkForRelease := nil; + Exit(S_OK); + end; // if CF_VTHEADERREFERENCE + + + // The tree reference format is always supported and returned from here. + if (FormatEtcIn.cfFormat = CF_VTREFERENCE) and Assigned(FOWner) then + begin + // Note: this format is not used while flushing the clipboard to avoid a dangling reference + // when the owner tree is destroyed before the clipboard data is replaced with something else. + if tsClipboardFlushing in TBaseVirtualTree(FOwner).TreeStates then + Result := E_FAIL + else + begin + Medium.HGlobal := GlobalAlloc(GHND or GMEM_SHARE, SizeOf(TVTReference)); + Data := GlobalLock(Medium.HGlobal); + Data.Process := GetCurrentProcessID; + Data.Tree := TBaseVirtualTree(FOwner); + GlobalUnLock(Medium.HGlobal); + Medium.tymed := TYMED_HGLOBAL; + Medium.unkForRelease := nil; + Exit(S_OK); + end; + end; // if CF_VTREFERENCE + + try + // See if we accept this type and if not get the correct return value. + Result := QueryGetData(FormatEtcIn); + if Result = S_OK then + begin + for I := 0 to High(FormatEtcArray) do + begin + if EqualFormatEtc(FormatEtcIn, FormatEtcArray[I]) then + begin + if not RenderInternalOLEData(FormatEtcIn, Medium, Result) then + Result := TVTCracker(FOwner).RenderOLEData(FormatEtcIn, Medium, FForClipboard); + Break; + end; + end; + end; + except + ZeroMemory(@Medium, SizeOf(Medium)); + Result := E_FAIL; + end; // try..except +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.GetDataHere(const FormatEtc : TFormatEtc; out Medium : TStgMedium) : HResult; +begin + Result := E_NOTIMPL; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.QueryGetData(const FormatEtc : TFormatEtc) : HResult; +var + I : Integer; +begin + Result := DV_E_CLIPFORMAT; + for I := 0 to High(FFormatEtcArray) do + begin + if FormatEtc.cfFormat = FFormatEtcArray[I].cfFormat then + begin + if (FormatEtc.tymed and FFormatEtcArray[I].tymed) <> 0 then + begin + if FormatEtc.dwAspect = FFormatEtcArray[I].dwAspect then + begin + if FormatEtc.lindex = FFormatEtcArray[I].lindex then + begin + Result := S_OK; + Break; + end + else + Result := DV_E_LINDEX; + end + else + Result := DV_E_DVASPECT; + end + else + Result := DV_E_TYMED; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDataObject.SetData(const FormatEtc : TFormatEtc; var Medium : TStgMedium; DoRelease : BOOL) : HResult; +// Allows dynamic adding to the IDataObject during its existance. Most noteably it is used to implement +// IDropSourceHelper and allows to set a special format for optimized moves during a shell transfer. +var + Index : Integer; + LocalStgMedium : PStgMedium; +begin + // See if we already have a format of that type available. + Index := FindFormatEtc(FormatEtc, FormatEtcArray); + if Index > - 1 then + begin + // Just use the TFormatEct in the array after releasing the data. + LocalStgMedium := FindInternalStgMedium(FormatEtcArray[Index].cfFormat); + if Assigned(LocalStgMedium) then + begin + ReleaseStgMedium(LocalStgMedium^); + ZeroMemory(LocalStgMedium, SizeOf(LocalStgMedium^)); + end; + end + else + begin + // It is a new format so create a new TFormatCollectionItem, copy the + // FormatEtc parameter into the new object and and put it in the list. + SetLength(FFormatEtcArray, Length(FormatEtcArray) + 1); + FormatEtcArray[High(FormatEtcArray)] := FormatEtc; + + // Create a new InternalStgMedium and initialize it and associate it with the format. + SetLength(FInternalStgMediumArray, Length(InternalStgMediumArray) + 1); + InternalStgMediumArray[High(InternalStgMediumArray)].Format := FormatEtc.cfFormat; + LocalStgMedium := @InternalStgMediumArray[High(InternalStgMediumArray)].Medium; + ZeroMemory(LocalStgMedium, SizeOf(LocalStgMedium^)); + end; + + if DoRelease then + begin + // We are simply being given the data and we take control of it. + LocalStgMedium^ := Medium; + Result := S_OK; + end + else + begin + // We need to reference count or copy the data and keep our own references to it. + Result := StgMediumIncRef(Medium, LocalStgMedium^, True, Self as IDataObject); + + // Can get a circular reference if the client calls GetData then calls SetData with the same StgMedium. + // Because the unkForRelease for the IDataObject can be marshalled it is necessary to get pointers that + // can be correctly compared. See the IDragSourceHelper article by Raymond Chen at MSDN. + if Assigned(LocalStgMedium.unkForRelease) then + begin + if CanonicalIUnknown(Self) = CanonicalIUnknown(IUnknown(LocalStgMedium.unkForRelease)) then + IUnknown(LocalStgMedium.unkForRelease) := nil; // release the interface + end; + end; + + // Tell all registered advice sinks about the data change. + if Assigned(FAdviseHolder) then + FAdviseHolder.SendOnDataChange(Self as IDataObject, 0, 0); +end; + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.DragImage.pas b/components/virtualtreeview/Source/VirtualTrees.DragImage.pas new file mode 100644 index 000000000..1aa3b8789 --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.DragImage.pas @@ -0,0 +1,139 @@ +unit VirtualTrees.DragImage; + +interface + +uses + WinApi.Windows, + WinApi.ActiveX, + System.Types, + Vcl.Controls, + Vcl.Graphics; + +{$MINENUMSIZE 1, make enumerations as small as possible} + + +type + // Drag image support for the tree. + TVTTransparency = 0 .. 255; + + // Simple move limitation for the drag image. + TVTDragMoveRestriction = ( + dmrNone, + dmrHorizontalOnly, + dmrVerticalOnly + ); + + TVTDragImageStates = set of ( + disHidden, // Internal drag image is currently hidden (always hidden if drag image helper interfaces are used). + disInDrag, // Drag image class is currently being used. + disPrepared // Drag image class is prepared. + ); + + // Class to manage header and tree drag image during a drag'n drop operation. + TVTDragImage = class + private + FOwner : TCustomControl; + FBackImage, // backup of overwritten screen area + FAlphaImage, // target for alpha blending + FDragImage : TBitmap; // the actual drag image to blend to screen + FRestriction : TVTDragMoveRestriction; // determines in which directions the drag image can be moved + FColorKey : TColor; // color to make fully transparent regardless of any other setting + FStates : TVTDragImageStates; // Determines the states of the drag image class. + public + constructor Create(AOwner : TCustomControl); + destructor Destroy; override; + + procedure EndDrag; + procedure PrepareDrag(DragImage : TBitmap; HotSpot : TPoint; const DataObject: IDataObject; pColorKey: TColor = clWindow); + property MoveRestriction : TVTDragMoveRestriction read FRestriction write FRestriction default dmrNone; + end; + +implementation + +uses + WinApi.ShlObj, + WinApi.Messages, + System.SysUtils, + System.Math, + VirtualTrees.DragnDrop, + VirtualTrees.Types, + VirtualTrees.Utils, + VirtualTrees.BaseTree; + +//----------------- TVTDragImage --------------------------------------------------------------------------------------- + +constructor TVTDragImage.Create(AOwner : TCustomControl); +begin + FOwner := AOwner; + FRestriction := dmrNone; + FColorKey := clNone; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +destructor TVTDragImage.Destroy; +begin + EndDrag; + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTDragImage.EndDrag; +begin + FStates := FStates - [disInDrag, disPrepared]; + FBackImage.Free; + FBackImage := nil; + FDragImage.Free; + FDragImage := nil; + FAlphaImage.Free; + FAlphaImage := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTDragImage.PrepareDrag(DragImage : TBitmap; HotSpot : TPoint; const DataObject : IDataObject; pColorKey: TColor = clWindow); +// Creates all necessary structures to do alpha blended dragging using the given image. +// ImagePostion and HotSpot are given in screen coordinates. The first determines where to place the drag image while +// the second is the initial mouse position. +// This method also determines whether the system supports drag images natively. If so then only minimal structures +// are created. + +var + Width, Height : Integer; + DragSourceHelper : IDragSourceHelper; + DragInfo : TSHDragImage; + lDragSourceHelper2 : IDragSourceHelper2; // Needed to get Windows Vista+ style drag hints. + lNullPoint : TPoint; +begin + Width := DragImage.Width; + Height := DragImage.Height; + FColorKey := pColorKey; + + // Determine whether the system supports the drag helper interfaces. + if Assigned(DataObject) and Succeeded(CoCreateInstance(CLSID_DragDropHelper, nil, CLSCTX_INPROC_SERVER, IDragSourceHelper, DragSourceHelper)) then + begin + lNullPoint := Point(0, 0); + if Supports(DragSourceHelper, IDragSourceHelper2, lDragSourceHelper2) then + lDragSourceHelper2.SetFlags(DSH_ALLOWDROPDESCRIPTIONTEXT); // Show description texts + // First let the system try to initialze the DragSourceHelper, this works fine for file system objects (CF_HDROP) + StandardOLEFormat.cfFormat := CF_HDROP; + if not Succeeded(DataObject.QueryGetData(StandardOLEFormat)) or not Succeeded(DragSourceHelper.InitializeFromWindow(0, lNullPoint, DataObject)) then + begin + // Supply the drag source helper with our drag image. + DragInfo.sizeDragImage.cx := Width; + DragInfo.sizeDragImage.cy := Height; + DragInfo.ptOffset := HotSpot; + DragInfo.hbmpDragImage := CopyImage(DragImage.Handle, IMAGE_BITMAP, Width, Height, LR_COPYRETURNORG); + DragInfo.crColorKey := ColorToRGB(FColorKey); + if not Succeeded(DragSourceHelper.InitializeFromBitmap(@DragInfo, DataObject)) then + begin + DeleteObject(DragInfo.hbmpDragImage); + end; + end; + end; +end; + + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.DragnDrop.pas b/components/virtualtreeview/Source/VirtualTrees.DragnDrop.pas new file mode 100644 index 000000000..4eb54873e --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.DragnDrop.pas @@ -0,0 +1,377 @@ +unit VirtualTrees.DragnDrop; + +interface + +uses + WinApi.Windows, + WinApi.ActiveX, + WinApi.ShlObj, + System.Types, + Vcl.Graphics, + Vcl.Controls, + VirtualTrees.Types, + VirtualTrees.BaseTree, + VirtualTrees.Header; + +type + TEnumFormatEtc = class(TInterfacedObject, IEnumFormatEtc) + private + FFormatEtcArray : TFormatEtcArray; + FCurrentIndex : Integer; + public + constructor Create(const AFormatEtcArray : TFormatEtcArray); + + function Clone(out Enum : IEnumFormatEtc) : HResult; stdcall; + function Next(celt : Integer; out elt; pceltFetched : PLongint) : HResult; stdcall; + function Reset : HResult; stdcall; + function Skip(celt : Integer) : HResult; stdcall; + end; + + // TVTDragManager is a class to manage drag and drop in a Virtual Treeview. + TVTDragManager = class(TInterfacedObject, IVTDragManager, IDropSource, IDropTarget) + private + FOwner, // The tree which is responsible for drag management. + FDragSource : TBaseVirtualTree; // Reference to the source tree if the source was a VT, might be different than the owner tree. + FHeader : TVTHeader; + FIsDropTarget : Boolean; // True if the owner is currently the drop target. + FDataObject : IDataObject; // A reference to the data object passed in by DragEnter (only used when the owner tree is the current drop target). + FDropTargetHelper : IDropTargetHelper; // Win2k > Drag image support + FFullDragging : BOOL; // True, if full dragging is currently enabled in the system. + + function GetDataObject : IDataObject; stdcall; + function GetDragSource : TBaseVirtualTree; stdcall; + function GetIsDropTarget : Boolean; stdcall; + public + constructor Create(AOwner : TBaseVirtualTree); virtual; + destructor Destroy; override; + + function DragEnter(const DataObject : IDataObject; KeyState : Integer; Pt : TPoint; var Effect : Longint) : HResult; stdcall; + function DragLeave : HResult; stdcall; + function DragOver(KeyState : Integer; Pt : TPoint; var Effect : Longint) : HResult; stdcall; + function Drop(const DataObject : IDataObject; KeyState : Integer; Pt : TPoint; var Effect : Integer) : HResult; stdcall; + procedure ForceDragLeave; stdcall; + function GiveFeedback(Effect : Integer) : HResult; stdcall; + function QueryContinueDrag(EscapePressed : BOOL; KeyState : Integer) : HResult; stdcall; + class function GetTreeFromDataObject(const DataObject: TVTDragDataObject): TBaseVirtualTree; + end; + +var + StandardOLEFormat : TFormatEtc = ( + // Format must later be set. + cfFormat : 0; + // No specific target device to render on. + ptd : nil; + // Normal content to render. + dwAspect : DVASPECT_CONTENT; + // No specific page of multipage data (we don't use multipage data by default). + lindex : - 1; + // Acceptable storage formats are IStream and global memory. The first is preferred. + tymed : TYMED_ISTREAM or TYMED_HGLOBAL; + ); + +implementation + +uses + VirtualTrees.Clipboard, + VirtualTrees.DataObject; + +type + TBaseVirtualTreeCracker = class(TBaseVirtualTree); + + TVTDragManagerHelper = class helper for TVTDragManager + function TreeView : TBaseVirtualTreeCracker; + end; + + + //----------------- TEnumFormatEtc ------------------------------------------------------------------------------------- + +constructor TEnumFormatEtc.Create(const AFormatEtcArray : TFormatEtcArray); +var + I : Integer; +begin + inherited Create; + // Make a local copy of the format data. + SetLength(FFormatEtcArray, Length(AFormatEtcArray)); + for I := 0 to High(AFormatEtcArray) do + FFormatEtcArray[I] := AFormatEtcArray[I]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TEnumFormatEtc.Clone(out Enum : IEnumFormatEtc) : HResult; +var + AClone : TEnumFormatEtc; +begin + Result := S_OK; + try + AClone := TEnumFormatEtc.Create(FFormatEtcArray); + AClone.FCurrentIndex := FCurrentIndex; + Enum := AClone as IEnumFormatEtc; + except + Result := E_FAIL; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TEnumFormatEtc.Next(celt : Integer; out elt; pceltFetched : PLongint) : HResult; +var + CopyCount : Integer; +begin + Result := S_FALSE; + CopyCount := Length(FFormatEtcArray) - FCurrentIndex; + if celt < CopyCount then + CopyCount := celt; + if CopyCount > 0 then + begin + Move(FFormatEtcArray[FCurrentIndex], elt, CopyCount * SizeOf(TFormatEtc)); + Inc(FCurrentIndex, CopyCount); + Result := S_OK; + end; + if Assigned(pceltFetched) then + pceltFetched^ := CopyCount; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TEnumFormatEtc.Reset : HResult; +begin + FCurrentIndex := 0; + Result := S_OK; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TEnumFormatEtc.Skip(celt : Integer) : HResult; +begin + if FCurrentIndex + celt < High(FFormatEtcArray) then + begin + Inc(FCurrentIndex, celt); + Result := S_OK; + end + else + Result := S_FALSE; +end; + + +//---------------------------------------------------------------------------------------------------------------------- + +// OLE drag and drop support classes +// This is quite heavy stuff (compared with the VCL implementation) but is much better suited to fit the needs +// of DD'ing various kinds of virtual data and works also between applications. + + +//----------------- TVTDragManager ------------------------------------------------------------------------------------- + +constructor TVTDragManager.Create(AOwner : TBaseVirtualTree); +begin + inherited Create; + FOwner := AOwner; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +destructor TVTDragManager.Destroy; +begin + // Set the owner's reference to us to nil otherwise it will access an invalid pointer + // after our desctruction is complete. + TreeView.ClearDragManager; + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDragManager.GetDataObject : IDataObject; +begin + // When the owner tree starts a drag operation then it gets a data object here to pass it to the OLE subsystem. + // In this case there is no local reference to a data object and one is created (but not stored). + // If there is a local reference then the owner tree is currently the drop target and the stored interface is + // that of the drag initiator. + if Assigned(FDataObject) then + Result := FDataObject + else + begin + Result := TreeView.DoCreateDataObject; + if (Result = nil) and not Assigned(TreeView.OnCreateDataObject) then + // Do not create a TVTDataObject if the event handler explicitely decided not to supply one, issue #736. + Result := TVTDataObject.Create(FOwner, False) as IDataObject; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDragManager.GetDragSource : TBaseVirtualTree; +begin + Result := FDragSource; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDragManager.GetIsDropTarget : Boolean; +begin + Result := FIsDropTarget; +end; + +class function TVTDragManager.GetTreeFromDataObject(const DataObject: TVTDragDataObject): TBaseVirtualTree; +// Returns the owner/sender of the given data object by means of a special clipboard format +// or nil if the sender is in another process or no virtual tree at all. + +var + Medium: TStgMedium; + Data: PVTReference; + +begin + Result := nil; + if Assigned(DataObject) then + begin + StandardOLEFormat.cfFormat := CF_VTREFERENCE; + if DataObject.GetData(StandardOLEFormat, Medium) = S_OK then + begin + Data := GlobalLock(Medium.hGlobal); + if Assigned(Data) then + begin + if Data.Process = GetCurrentProcessID then + Result := Data.Tree; + GlobalUnlock(Medium.hGlobal); + end; + ReleaseStgMedium(Medium); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDragManager.DragEnter(const DataObject : IDataObject; KeyState : Integer; Pt : TPoint; var Effect : Integer) : HResult; +var + Medium: TStgMedium; + HeaderFormatEtc: TFormatEtc; +begin + if not Assigned(FDropTargetHelper) then + CoCreateInstance(CLSID_DragDropHelper, nil, CLSCTX_INPROC_SERVER, IID_IDropTargetHelper, FDropTargetHelper); + + FDataObject := DataObject; + FIsDropTarget := True; + + SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, @FFullDragging, 0); + // If full dragging of window contents is disabled in the system then our tree windows will be locked + // and cannot be updated during a drag operation. With the following call painting is again enabled. + if not FFullDragging then + LockWindowUpdate(0); + if Assigned(FDropTargetHelper) and FFullDragging then + begin + if (toAutoScroll in TreeView.TreeOptions.AutoOptions) and (toAcceptOLEDrop in TreeView.TreeOptions.MiscOptions) then + FDropTargetHelper.DragEnter(FOwner.Handle, DataObject, Pt, Effect) + else + FDropTargetHelper.DragEnter(0, DataObject, Pt, Effect); // Do not pass handle, otherwise the IDropTargetHelper will perform autoscroll. Issue #486 + end; + FDragSource := GetTreeFromDataObject(DataObject); + Result := TreeView.DragEnter(KeyState, Pt, Effect); + HeaderFormatEtc := StandardOLEFormat; + HeaderFormatEtc.cfFormat := CF_VTHEADERREFERENCE; + if (DataObject.GetData(HeaderFormatEtc, Medium) = S_OK) and (FDragSource = FOWner) then + begin + FHeader := FDragSource.Header; + FDRagSource := nil; + end + else + begin + fHeader := nil; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDragManager.DragLeave : HResult; +begin + if Assigned(FDropTargetHelper) and FFullDragging then + FDropTargetHelper.DragLeave; + + if (toAcceptOLEDrop in TreeView.TreeOptions.MiscOptions) then + TreeView.DragLeave; + FIsDropTarget := False; + FDragSource := nil; + FDataObject := nil; + fHeader := nil; + Result := NOERROR; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDragManager.DragOver(KeyState : Integer; Pt : TPoint; var Effect : Integer) : HResult; +begin + if Assigned(FDropTargetHelper) and FFullDragging then + FDropTargetHelper.DragOver(Pt, Effect); + + Result := NOERROR; + if Assigned(fHeader) then + begin + TreeView.Header.DragTo(Pt); + end + else if (toAcceptOLEDrop in TreeView.TreeOptions.MiscOptions) then + Result := TreeView.DragOver(FDragSource, KeyState, dsDragMove, Pt, Effect); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDragManager.Drop(const DataObject : IDataObject; KeyState : Integer; Pt : TPoint; var Effect : Integer) : HResult; +begin + if Assigned(FDropTargetHelper) and FFullDragging then + FDropTargetHelper.Drop(DataObject, Pt, Effect); + + if Assigned(fHeader) then + begin + FHeader.ColumnDropped(Pt); + Result := NO_ERROR; + end + else + Result := TreeView.DragDrop(DataObject, KeyState, Pt, Effect); + FIsDropTarget := False; + FDataObject := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTDragManager.ForceDragLeave; +// Some drop targets, e.g. Internet Explorer leave a drag image on screen instead removing it when they receive +// a drop action. This method calls the drop target helper's DragLeave method to ensure it removes the drag image from +// screen. Unfortunately, sometimes not even this does help (e.g. when dragging text from VT to a text field in IE). +begin + if Assigned(FDropTargetHelper) and FFullDragging then + FDropTargetHelper.DragLeave; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDragManager.GiveFeedback(Effect : Integer) : HResult; +begin + Result := DRAGDROP_S_USEDEFAULTCURSORS; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTDragManager.QueryContinueDrag(EscapePressed : BOOL; KeyState : Integer) : HResult; +var + RButton, LButton : Boolean; +begin + LButton := (KeyState and MK_LBUTTON) <> 0; + RButton := (KeyState and MK_RBUTTON) <> 0; + + // Drag'n drop canceled by pressing both mouse buttons or Esc? + if (LButton and RButton) or EscapePressed then + Result := DRAGDROP_S_CANCEL + else + // Drag'n drop finished? + if not (LButton or RButton) then + Result := DRAGDROP_S_DROP + else + Result := S_OK; +end; + +{ TVTDragManagerHelper } + +function TVTDragManagerHelper.TreeView : TBaseVirtualTreeCracker; +begin + Result := TBaseVirtualTreeCracker(FOwner); +end; + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.DrawTree.pas b/components/virtualtreeview/Source/VirtualTrees.DrawTree.pas new file mode 100644 index 000000000..0fb1e38d4 --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.DrawTree.pas @@ -0,0 +1,354 @@ +unit VirtualTrees.DrawTree; + +interface + +uses + System.Types, + System.Classes, + Vcl.Themes, + VirtualTrees.Types, + VirtualTrees.BaseTree, +{$IFDEF VT_FMX} + VirtualTrees.AncestorFMX, +{$ELSE} + VirtualTrees.AncestorVCL +{$ENDIF} + ; + +type +{$IFDEF VT_FMX} + TVTAncestor = TVTAncestorFMX; +{$ELSE} + TVTAncestor = TVTAncestorVcl; +{$ENDIF} + + // Tree descendant to let an application draw its stuff itself. + TCustomVirtualDrawTree = class(TVTAncestor) + private + FOnDrawNode: TVTDrawNodeEvent; + FOnGetCellContentMargin: TVTGetCellContentMarginEvent; + FOnGetNodeWidth: TVTGetNodeWidthEvent; + protected + function DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex; + CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; override; + function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): TDimension; override; + procedure DoPaintNode(var PaintInfo: TVTPaintInfo); override; + function GetDefaultHintKind: TVTHintKind; override; + + property OnDrawNode: TVTDrawNodeEvent read FOnDrawNode write FOnDrawNode; + property OnGetCellContentMargin: TVTGetCellContentMarginEvent read FOnGetCellContentMargin write FOnGetCellContentMargin; + property OnGetNodeWidth: TVTGetNodeWidthEvent read FOnGetNodeWidth write FOnGetNodeWidth; + end; + + {$if CompilerVersion >= 33} + [ComponentPlatformsAttribute(pfidWindows)] + {$ifend} + TVirtualDrawTree = class(TCustomVirtualDrawTree) + private + function GetOptions: TVirtualTreeOptions; + procedure SetOptions(const Value: TVirtualTreeOptions); + protected + function GetOptionsClass: TTreeOptionsClass; override; + public + property Canvas; + property LastDragEffect; + property CheckImageKind; // should no more be published to make #622 fix working + published + property Action; + property Align; + property Alignment; + property Anchors; + property AnimationDuration; + property AutoExpandDelay; + property AutoScrollDelay; + property AutoScrollInterval; + property Background; + property BackgroundOffsetX; + property BackgroundOffsetY; + property BiDiMode; + property BevelEdges; + property BevelInner; + property BevelOuter; + property BevelKind; + property BevelWidth; + property BorderStyle; + property BottomSpace; + property ButtonFillMode; + property ButtonStyle; + property BorderWidth; + property ChangeDelay; + property ClipboardFormats; + property Color; + property Colors; + property Constraints; + property Ctl3D; + property CustomCheckImages; + property DefaultNodeHeight; + property DefaultPasteMode; + property DragCursor; + property DragHeight; + property DragKind; + property DragImageKind; + property DragMode; + property DragOperations; + property DragType; + property DragWidth; + property DrawSelectionMode; + property EditDelay; + property Enabled; + property Font; + property Header; + property HintMode; + property HotCursor; + property Images; + property IncrementalSearch; + property IncrementalSearchDirection; + property IncrementalSearchStart; + property IncrementalSearchTimeout; + property Indent; + property LineMode; + property LineStyle; + property Margin; + property NodeAlignment; + property NodeDataSize; + property OperationCanceled; + property ParentBiDiMode; + property ParentColor default False; + property ParentCtl3D; + property ParentFont; + property ParentShowHint; + property PopupMenu; + property RootNodeCount; + property ScrollBarOptions; + property SelectionBlendFactor; + property SelectionCurveRadius; + property ShowHint; + property StateImages; + property TabOrder; + property TabStop default True; + property TextMargin; + property TreeOptions: TVirtualTreeOptions read GetOptions write SetOptions; + property Visible; + property WantTabs; + + property OnAddToSelection; + property OnAdvancedHeaderDraw; + property OnAfterAutoFitColumn; + property OnAfterAutoFitColumns; + property OnAfterCellPaint; + property OnAfterColumnExport; + property OnAfterColumnWidthTracking; + property OnAfterGetMaxColumnWidth; + property OnAfterHeaderExport; + property OnAfterHeaderHeightTracking; + property OnAfterItemErase; + property OnAfterItemPaint; + property OnAfterNodeExport; + property OnAfterPaint; + property OnAfterTreeExport; + property OnBeforeAutoFitColumn; + property OnBeforeAutoFitColumns; + property OnBeforeCellPaint; + property OnBeforeColumnExport; + property OnBeforeColumnWidthTracking; + property OnBeforeDrawTreeLine; + property OnBeforeGetMaxColumnWidth; + property OnBeforeHeaderExport; + property OnBeforeHeaderHeightTracking; + property OnBeforeItemErase; + property OnBeforeItemPaint; + property OnBeforeNodeExport; + property OnBeforePaint; + property OnBeforeTreeExport; + property OnCanSplitterResizeColumn; + property OnCanSplitterResizeHeader; + property OnCanSplitterResizeNode; + property OnChange; + property OnChecked; + property OnChecking; + property OnClick; + property OnCollapsed; + property OnCollapsing; + property OnColumnChecked; + property OnColumnChecking; + property OnColumnClick; + property OnColumnDblClick; + property OnColumnExport; + property OnColumnResize; + property OnColumnVisibilityChanged; + property OnColumnWidthDblClickResize; + property OnColumnWidthTracking; + property OnCompareNodes; + property OnContextPopup; + property OnCreateDataObject; + property OnCreateDragManager; + property OnCreateEditor; + property OnDblClick; + property OnDragAllowed; + property OnDragOver; + property OnDragDrop; + property OnDrawHint; + property OnDrawNode; + property OnEdited; + property OnEditing; + property OnEndDock; + property OnEndDrag; + property OnEndOperation; + property OnEnter; + property OnExit; + property OnExpanded; + property OnExpanding; + property OnFocusChanged; + property OnFocusChanging; + property OnFreeNode; + property OnGetCellIsEmpty; + property OnGetCursor; + property OnGetHeaderCursor; + property OnGetHelpContext; + property OnGetHintKind; + property OnGetHintSize; + property OnGetImageIndex; + property OnGetImageIndexEx; + property OnGetLineStyle; + property OnGetNodeDataSize; + property OnGetNodeWidth; + property OnGetPopupMenu; + property OnGetUserClipboardFormats; + property OnHeaderAddPopupItem; + property OnHeaderClick; + property OnHeaderDblClick; + property OnHeaderDragged; + property OnHeaderDraggedOut; + property OnHeaderDragging; + property OnHeaderDraw; + property OnHeaderDrawQueryElements; + property OnHeaderHeightTracking; + property OnHeaderHeightDblClickResize; + property OnHeaderMouseDown; + property OnHeaderMouseMove; + property OnHeaderMouseUp; + property OnHotChange; + property OnIncrementalSearch; + property OnInitChildren; + property OnInitNode; + property OnKeyAction; + property OnKeyDown; + property OnKeyPress; + property OnKeyUp; + property OnLoadNode; + property OnLoadTree; + property OnMeasureItem; + property OnMouseDown; + property OnMouseMove; + property OnMouseUp; + property OnMouseWheel; + property OnNodeClick; + property OnNodeCopied; + property OnNodeCopying; + property OnNodeDblClick; + property OnNodeExport; + property OnNodeHeightTracking; + property OnNodeHeightDblClickResize; + property OnNodeMoved; + property OnNodeMoving; + property OnPaintBackground; + property OnPrepareButtonBitmaps; + property OnRemoveFromSelection; + property OnRenderOLEData; + property OnResetNode; + property OnResize; + property OnSaveNode; + property OnSaveTree; + property OnScroll; + property OnShowScrollBar; + property OnStartDock; + property OnStartDrag; + property OnStartOperation; + property OnStateChange; + property OnStructureChange; + property OnUpdating; + property OnCanResize; + property OnGesture; + property Touch; + property StyleElements; + end; + + +implementation + +uses + VirtualTrees.StyleHooks; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCustomVirtualDrawTree.DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex; + CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; + +begin + Result := Point(0, 0); + if Canvas = nil then + Canvas := Self.Canvas; + + if Assigned(FOnGetCellContentMargin) then + FOnGetCellContentMargin(Self, Canvas, Node, Column, CellContentMarginType, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCustomVirtualDrawTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): TDimension; + +begin + Result := 2 * TextMargin; + if Canvas = nil then + Canvas := Self.Canvas; + + if Assigned(FOnGetNodeWidth) then + FOnGetNodeWidth(Self, Canvas, Node, Column, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCustomVirtualDrawTree.DoPaintNode(var PaintInfo: TVTPaintInfo); + +begin + if Assigned(FOnDrawNode) then + FOnDrawNode(Self, PaintInfo); +end; + +function TCustomVirtualDrawTree.GetDefaultHintKind: TVTHintKind; + +begin + Result := vhkOwnerDraw; +end; + +//----------------- TVirtualDrawTree ----------------------------------------------------------------------------------- + +function TVirtualDrawTree.GetOptions: TVirtualTreeOptions; + +begin + Result := inherited TreeOptions as TVirtualTreeOptions; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualDrawTree.SetOptions(const Value: TVirtualTreeOptions); + +begin + TreeOptions.Assign(Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualDrawTree.GetOptionsClass: TTreeOptionsClass; + +begin + Result := TVirtualTreeOptions; +end; + +initialization + TCustomStyleEngine.RegisterStyleHook(TVirtualDrawTree, TVclStyleScrollBarsHook); + +finalization + TCustomStyleEngine.UnRegisterStyleHook(TVirtualDrawTree, TVclStyleScrollBarsHook); + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.EditLink.pas b/components/virtualtreeview/Source/VirtualTrees.EditLink.pas new file mode 100644 index 000000000..6af02dd35 --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.EditLink.pas @@ -0,0 +1,905 @@ +unit VirtualTrees.EditLink; + +// Base class for inplace node editors implementing IVTEditLink interface +// and default node editor. + +interface + +uses + WinApi.Messages, + System.Types, + System.Classes, + Vcl.Controls, + Vcl.StdCtrls, + VirtualTrees, + VirtualTrees.Types, + VirtualTrees.BaseTree; + +type + //Edit support Classes. + TStringEditLink = class; + + TVTEdit = class(TCustomEdit) + private + procedure CMAutoAdjust(var Message : TMessage); message CM_AUTOADJUST; + procedure CMExit(var Message : TMessage); message CM_EXIT; + procedure CMRelease(var Message : TMessage); message CM_RELEASE; + procedure CNCommand(var Message : TWMCommand); message CN_COMMAND; + procedure WMChar(var Message : TWMChar); message WM_CHAR; + procedure WMDestroy(var Message : TWMDestroy); message WM_DESTROY; + procedure WMGetDlgCode(var Message : TWMGetDlgCode); message WM_GETDLGCODE; + procedure WMKeyDown(var Message : TWMKeyDown); message WM_KEYDOWN; + protected + FRefLink : IVTEditLink; + FLink : TStringEditLink; + procedure AutoAdjustSize; virtual; + function CalcMinHeight : Integer; virtual; + procedure CreateParams(var Params : TCreateParams); override; + function GetTextSize : TSize; virtual; + procedure KeyPress(var Key : Char); override; + public + constructor Create(Link : TStringEditLink); reintroduce; + procedure ClearLink; + procedure ClearRefLink; + procedure Release; virtual; + + property AutoSelect; + property AutoSize; + property BorderStyle; + property CharCase; + property HideSelection; + property MaxLength; + property OEMConvert; + property PasswordChar; + end; + + TBaseEditLink = class; + + TEditLinkEditEvent = procedure (Sender: TBaseEditLink; var Result: Boolean) of object; + TEditLinkPrepareEditEvent = procedure (Sender: TBaseEditLink; var Edit: TControl; var Result: Boolean) of object; + + // Most abstract base class for implementing IVTEditLink. + // Knows almost nothing about associated Edit control and doesn't perform any + // actions on it. Contains some properties that are not used directly but could + // be useful in descendant classes. Follows general extension approach - all + // IVTEditLink methods are virtual and most of them call DoXXX virtual methods + // which in turn call event handlers so these extension options possible: + // - overriding main API methods to run additional actions before, after or + // instead of basic class code. + // (+) Lesser modification of existing classes + // (-) Event handlers are already launched after calling parent method + // (-) It's critical to check Result of parent method and exit immediately + // on False - this value means no action is done. + // (-) Returning Result is necessary + // - overriding DoXXX methods to run additional actions inside basic class code + // (+) No need in returning - lesser boilerplate code + // (-) Should call inherited to launch event handlers (OK if not using them) + // - assign event handlers in end-user code + // (+) Access to external classes with data to copy to EditLink editor. + // (-) Lesser encapsulation + TBaseEditLink = class(TInterfacedObject, IVTEditLink) + strict protected + FEdit: TControl; // One of the property editor classes. + FTree : TCustomVirtualStringTree; //A back reference to the tree calling. + FNode : PVirtualNode; //The node to be edited. + FColumn : TColumnIndex; //The column of the node. + FStopping : Boolean; //Set to True when the edit link requests stopping the edit action. + FAlignment : TAlignment; + FBiDiMode: TBiDiMode; + + // custom event handlers + FOnPrepareEdit: TEditLinkPrepareEditEvent; + FOnBeginEdit, + FOnEndEdit, + FOnCancelEdit: TEditLinkEditEvent; + + procedure SetEdit(const Value : TControl); //Setter for the FEdit member; + public + // IVTEditLink API + function BeginEdit : Boolean; virtual; stdcall; + function CancelEdit : Boolean; virtual; stdcall; + function EndEdit : Boolean; virtual; stdcall; + function GetBounds : TRect; virtual; stdcall; abstract; + function PrepareEdit(Tree : TBaseVirtualTree; Node : PVirtualNode; Column : TColumnIndex) : Boolean; virtual; stdcall; + procedure ProcessMessage(var Message : TMessage); virtual; stdcall; abstract; + procedure SetBounds(R : TRect); virtual; stdcall; abstract; + + // Methods to plug custom actions into main ones. In base class only call event handlers. + // Descendants may modify Result to cancel further flow. + procedure DoBeginEdit(var Result: Boolean); virtual; + procedure DoCancelEdit(var Result: Boolean); virtual; + procedure DoEndEdit(var Result: Boolean); virtual; + procedure DoPrepareEdit(var Result: Boolean); virtual; + + property Alignment : TAlignment read FAlignment; + property BiDiMode: TBiDiMode read FBiDiMode; + property Column : TColumnIndex read FColumn; //[IPK] Make Column(Index) accessible + property Node : PVirtualNode read FNode; //[IPK] Make FNode accessible + property Tree : TCustomVirtualStringTree read FTree; + property Stopping : Boolean read FStopping; + + property OnBeginEdit: TEditLinkEditEvent read FOnBeginEdit write FOnBeginEdit; + property OnCancelEdit: TEditLinkEditEvent read FOnCancelEdit write FOnCancelEdit; + property OnEndEdit: TEditLinkEditEvent read FOnEndEdit write FOnEndEdit; + property OnPrepareEdit: TEditLinkPrepareEditEvent read FOnPrepareEdit write FOnPrepareEdit; + end; + + // Edit link that has TWinControl-based Edit. Performs visibility and focus actions, + // transfers window messages to Edit control. + TWinControlEditLink = class(TBaseEditLink) + protected + function GetEdit: TWinControl; //Getter for the FEdit member; + procedure SetEdit(const Value : TWinControl); //Setter for the FEdit member; + public + destructor Destroy; override; + + function BeginEdit : Boolean; override; stdcall; + function CancelEdit : Boolean; override; stdcall; + function EndEdit : Boolean; override; stdcall; + function GetBounds : TRect; override; stdcall; + procedure ProcessMessage(var Message : TMessage); override; stdcall; + + property Edit : TWinControl read GetEdit write SetEdit; + end; + + // Edit link that implements default node text editor. + TStringEditLink = class(TWinControlEditLink) + protected + FTextBounds : TRect; //Smallest rectangle around the text. + function GetEdit: TVTEdit; //Getter for the FEdit member; + procedure SetEdit(const Value : TVTEdit); //Setter for the FEdit member; + + procedure InitializeSelection; virtual; + public + constructor Create; + + function BeginEdit : Boolean; override; stdcall; + function CancelEdit : Boolean; override; stdcall; + function EndEdit : Boolean; override; stdcall; + function PrepareEdit(Tree : TBaseVirtualTree; Node : PVirtualNode; Column : TColumnIndex) : Boolean; override; stdcall; + procedure SetBounds(R : TRect); override; stdcall; + + property Edit : TVTEdit read GetEdit write SetEdit; + end; + +implementation + +uses + WinApi.Windows, + System.SysUtils, + System.Math, + Vcl.Graphics, + Vcl.Forms; + +type + TCustomVirtualStringTreeCracker = class(TCustomVirtualStringTree); + +//----------------- TVTEdit -------------------------------------------------------------------------------------------- + +//Implementation of a generic node caption editor. + +constructor TVTEdit.Create(Link : TStringEditLink); +begin + inherited Create(nil); + if not Assigned(Link) then + raise EArgumentException.Create('Parameter Link must not be nil.'); + ShowHint := False; + ParentShowHint := False; + //This assignment increases the reference count for the interface. + FRefLink := Link; + //This reference is used to access the link. + FLink := Link; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.ClearLink; +begin + FLink := nil +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.ClearRefLink; +begin + FRefLink := nil +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTEdit.CalcMinHeight : Integer; +var + textHeight : Integer; +begin + //Get the actual text height. + textHeight := GetTextSize.cy; + //The minimal height is the actual text height in pixels plus the the non client area. + Result := textHeight + (Height - ClientHeight); + //Also, proportionally to the text size, additional pixel(s) needs to be added for the caret. + Result := Result + Trunc(textHeight * 0.05); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.CMAutoAdjust(var Message : TMessage); +begin + AutoAdjustSize; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.CMExit(var Message : TMessage); +begin + if Assigned(FLink) and not FLink.Stopping then + with TCustomVirtualStringTreeCracker(FLink.Tree) do + begin + if (toAutoAcceptEditChange in TreeOptions.StringOptions) then + DoEndEdit + else + DoCancelEdit; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.CMRelease(var Message : TMessage); +begin + Free; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.CNCommand(var Message : TWMCommand); +begin + if Assigned(FLink) and Assigned(FLink.Tree) and (Message.NotifyCode = EN_UPDATE) and not (vsMultiline in FLink.Node.States) then + //Instead directly calling AutoAdjustSize it is necessary on Win9x/Me to decouple this notification message + //and eventual resizing. Hence we use a message to accomplish that. + AutoAdjustSize() + else + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.WMChar(var Message : TWMChar); +begin + if not (Message.CharCode in [VK_ESCAPE, VK_TAB]) then + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.WMDestroy(var Message : TWMDestroy); +begin + //If editing stopped by other means than accept or cancel then we have to do default processing for + //pending changes. + if Assigned(FLink) and not FLink.Stopping and not (csRecreating in Self.ControlState) then + begin + with TCustomVirtualStringTreeCracker(FLink.Tree) do + begin + if (toAutoAcceptEditChange in TreeOptions.StringOptions) and Modified then + Text[FLink.Node, FLink.Column] := FLink.Edit.Text; + end; + FLink := nil; + FRefLink := nil; + end; + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.WMGetDlgCode(var Message : TWMGetDlgCode); +begin + inherited; + + Message.Result := Message.Result or DLGC_WANTALLKEYS or DLGC_WANTTAB or DLGC_WANTARROWS; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.WMKeyDown(var Message : TWMKeyDown); +//Handles some control keys. + +var + Shift : TShiftState; + EndEdit : Boolean; + Tree : TBaseVirtualTree; + NextNode : PVirtualNode; + ColumnCandidate : Integer; + EditOptions : TVTEditOptions; + Column : TVirtualTreeColumn; +begin + Tree := FLink.Tree; + case Message.CharCode of + VK_ESCAPE : + begin + TCustomVirtualStringTreeCracker(Tree).DoCancelEdit; + end; + VK_RETURN : + begin + EndEdit := not (vsMultiline in FLink.Node.States); + if not EndEdit then + begin + //If a multiline node is being edited the finish editing only if Ctrl+Enter was pressed, + //otherwise allow to insert line breaks into the text. + Shift := KeyDataToShiftState(Message.KeyData); + EndEdit := ssCtrl in Shift; + end; + if EndEdit then + begin + Tree := FLink.Tree; + FLink.Tree.InvalidateNode(FLink.Node); + NextNode := Tree.GetNextVisible(FLink.Node, True); + TCustomVirtualStringTreeCracker(FLink.Tree).DoEndEdit; + + //get edit options for column as priority. If column has toDefaultEdit + //use global edit options for tree + EditOptions := TCustomVirtualStringTreeCracker(Tree).TreeOptions.EditOptions; //default + ColumnCandidate := - 1; + if Tree.Header.Columns.Count > 0 then //are there any columns? + begin + Column := Tree.Header.Columns[Tree.FocusedColumn]; + if Column.EditOptions <> toDefaultEdit then + EditOptions := Column.EditOptions; + + //next column candidate for toVerticalEdit and toHorizontalEdit + if Column.EditNextColumn <> - 1 then + ColumnCandidate := Column.EditNextColumn; + end; + + case EditOptions of + toDefaultEdit : + TCustomVirtualStringTreeCracker(Tree).TrySetFocus; + toVerticalEdit : + if NextNode <> nil then + begin + Tree.FocusedNode := NextNode; + + //for toVerticalEdit ColumnCandidate is also proper, + //select ColumnCandidate column in row below + if ColumnCandidate <> - 1 then + begin + Tree.FocusedColumn := ColumnCandidate; + TCustomVirtualStringTreeCracker(Tree).EditColumn := ColumnCandidate; + end; + + if Tree.CanEdit(Tree.FocusedNode, Tree.FocusedColumn) then + TCustomVirtualStringTreeCracker(Tree).DoEdit; + end; + toHorizontalEdit : + begin + if ColumnCandidate = - 1 then + begin + //for toHorizontalEdit if property EditNextColumn is not used + //try to use just next column + ColumnCandidate := Tree.FocusedColumn + 1; + while (ColumnCandidate < Tree.Header.Columns.Count) and not Tree.CanEdit(Tree.FocusedNode, ColumnCandidate) do + Inc(ColumnCandidate); + end + else if not Tree.CanEdit(Tree.FocusedNode, ColumnCandidate) then + ColumnCandidate := Tree.Header.Columns.Count; //omit "focus/edit column" (see below) + + if ColumnCandidate < Tree.Header.Columns.Count then + begin + Tree.FocusedColumn := ColumnCandidate; + TCustomVirtualStringTreeCracker(Tree).EditColumn := ColumnCandidate; + TCustomVirtualStringTreeCracker(Tree).DoEdit; + end; + end; + end; + end; + end; + VK_UP : + begin + if not (vsMultiline in FLink.Node.States) then + Message.CharCode := VK_LEFT; + inherited; + end; + VK_DOWN : + begin + if not (vsMultiline in FLink.Node.States) then + Message.CharCode := VK_RIGHT; + inherited; + end; + VK_TAB : + begin + if Tree.IsEditing then + begin + Tree.InvalidateNode(FLink.Node); + if ssShift in KeyDataToShiftState(Message.KeyData) then + NextNode := Tree.GetPreviousVisible(FLink.Node, True)//Shift+Tab goes to previous mode + else + NextNode := Tree.GetNextVisible(FLink.Node, True); + Tree.EndEditNode; + //check NextNode, otherwise we got AV + if NextNode <> nil then + begin + //Continue editing next node + Tree.ClearSelection(); + Tree.Selected[NextNode] := True; + if Tree.CanEdit(Tree.FocusedNode, Tree.FocusedColumn) then + TCustomVirtualStringTreeCracker(Tree).DoEdit; + end; + end; + end; + Ord('A') : + begin + if Tree.IsEditing and ([ssCtrl] = KeyboardStateToShiftState) then + begin + Self.SelectAll(); + Message.CharCode := 0; + end; + end; + else + inherited; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.AutoAdjustSize; +//Changes the size of the edit to accomodate as much as possible of its text within its container window. +//NewChar describes the next character which will be added to the edit's text. + +var + Size : TSize; +begin + if not (vsMultiline in FLink.Node.States) and not (toGridExtensions in TCustomVirtualStringTreeCracker(FLink.Tree).TreeOptions.MiscOptions { see issue #252 } ) then + begin + //avoid flicker + SendMessage(Handle, WM_SETREDRAW, 0, 0); + try + Size := GetTextSize; + Inc(Size.cx, 2 * TCustomVirtualStringTreeCracker(FLink.Tree).TextMargin); + //Repaint associated node if the edit becomes smaller. + if Size.cx < Width then + FLink.Tree.Invalidate(); + + if FLink.Alignment = taRightJustify then + FLink.SetBounds(Rect(Left + Width - Size.cx, Top, Left + Width, Top + Max(Size.cy, Height))) + else + FLink.SetBounds(Rect(Left, Top, Left + Size.cx, Top + Max(Size.cy, Height))); + finally + SendMessage(Handle, WM_SETREDRAW, 1, 0); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.CreateParams(var Params : TCreateParams); +begin + inherited; + if not Assigned(FLink.Node) then + exit; //Prevent AV exceptions occasionally seen in code below + + //Only with multiline style we can use the text formatting rectangle. + //This does not harm formatting as single line control, if we don't use word wrapping. + with Params do + begin + Style := Style or ES_MULTILINE; + if vsMultiline in FLink.Node.States then + Style := Style and not (ES_AUTOHSCROLL or WS_HSCROLL) or WS_VSCROLL or ES_AUTOVSCROLL; + if tsUseThemes in FLink.Tree.TreeStates then + begin + Style := Style and not WS_BORDER; + ExStyle := ExStyle or WS_EX_CLIENTEDGE; + end + else + begin + Style := Style or WS_BORDER; + ExStyle := ExStyle and not WS_EX_CLIENTEDGE; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTEdit.GetTextSize : TSize; +var + DC : HDC; + LastFont : THandle; +begin + DC := GetDC(Handle); + LastFont := SelectObject(DC, Font.Handle); + try + //Read needed space for the current text. + GetTextExtentPoint32(DC, PChar(Text + 'yG'), Length(Text) + 2, Result); + finally + SelectObject(DC, LastFont); + ReleaseDC(Handle, DC); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- +procedure TVTEdit.KeyPress(var Key : Char); +begin + if (Key = #13) and Assigned(FLink) and not (vsMultiline in FLink.Node.States) then + Key := #0; //Filter out return keys as they will be added to the text, avoids #895 + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTEdit.Release; +begin + if HandleAllocated then + PostMessage(Handle, CM_RELEASE, 0, 0); +end; + +//----------------- TBaseEditLink -------------------------------------------------------------------------------------- + +procedure TBaseEditLink.SetEdit(const Value : TControl); +begin + if Assigned(FEdit) then + FEdit.Free; + FEdit := Value; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseEditLink.BeginEdit : Boolean; +//Notifies the edit link that editing can start now. descendants may cancel node edit +//by returning False. + +begin + Result := not FStopping; + if Result then + DoBeginEdit(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseEditLink.CancelEdit : Boolean; + +// Performs edit cancelling. + +begin + Result := not FStopping; + if Result then + begin + // Let descendants cancel the cancel + DoCancelEdit(Result); + if not Result then + Exit; + FStopping := True; + FTree.CancelEditNode; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseEditLink.EndEdit : Boolean; + +// Performs edit ending. + +begin + Result := not FStopping; + if Result then + begin + // Let descendants cancel the end + DoEndEdit(Result); + if not Result then + Exit; + FStopping := True; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TBaseEditLink.PrepareEdit(Tree : TBaseVirtualTree; Node : PVirtualNode; Column : TColumnIndex) : Boolean; + +// Performs general init: assign Tree, Node, Column, other properties; destroys previous +// edit instance. + +begin + Result := Tree is TCustomVirtualStringTree; + if not Result then Exit; // should not happen + + FTree := Tree as TCustomVirtualStringTree; + FNode := Node; + FColumn := Column; + if Column <= NoColumn then + begin + FBidiMode := FTree.BidiMode; + FAlignment := TCustomVirtualStringTreeCracker(FTree).Alignment; + end + else + begin + FBidiMode := FTree.Header.Columns[Column].BidiMode; + FAlignment := FTree.Header.Columns[Column].Alignment; + end; + SetEdit(nil); // always dispose edit + + DoPrepareEdit(Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseEditLink.DoBeginEdit(var Result: Boolean); +begin + if Assigned(OnBeginEdit) then + OnBeginEdit(Self, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseEditLink.DoCancelEdit(var Result: Boolean); +begin + if Assigned(OnCancelEdit) then + OnCancelEdit(Self, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseEditLink.DoEndEdit(var Result: Boolean); +begin + if Assigned(OnEndEdit) then + OnEndEdit(Self, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TBaseEditLink.DoPrepareEdit(var Result: Boolean); +begin + if Assigned(OnPrepareEdit) then + OnPrepareEdit(Self, FEdit, Result); +end; + +//----------------- TWinControlEditLink ------------------------------------------------------------------------------------ + +destructor TWinControlEditLink.Destroy; +begin + //FEdit.Free; casues issue #357. Fix: + if Assigned(FEdit) and Edit.HandleAllocated then + PostMessage(Edit.Handle, CM_RELEASE, 0, 0); + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TWinControlEditLink.GetEdit: TWinControl; +begin + Result := TWinControl(FEdit); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TWinControlEditLink.SetEdit(const Value: TWinControl); +begin + inherited SetEdit(Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TWinControlEditLink.BeginEdit: Boolean; +begin + Result := inherited; + if Result then + begin + Edit.Show; + Edit.SetFocus; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TWinControlEditLink.CancelEdit: Boolean; +begin + Result := inherited; + if Result then + begin + Edit.Hide; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TWinControlEditLink.GetBounds : TRect; +begin + Result := FEdit.BoundsRect; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TWinControlEditLink.ProcessMessage(var Message : TMessage); +begin + FEdit.WindowProc(Message); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TWinControlEditLink.EndEdit: Boolean; +begin + Result := inherited; + if Result then + begin + Edit.Hide; + end; +end; + +//----------------- TStringEditLink ------------------------------------------------------------------------------------ + +constructor TStringEditLink.Create; +begin + inherited; + FEdit := TVTEdit.Create(Self); + with Edit do + begin + Visible := False; + BorderStyle := bsSingle; + AutoSize := False; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TStringEditLink.GetEdit: TVTEdit; +begin + Result := TVTEdit(FEdit); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TStringEditLink.InitializeSelection; +begin + Edit.SelectAll; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TStringEditLink.SetEdit(const Value : TVTEdit); +begin + inherited SetEdit(Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TStringEditLink.BeginEdit : Boolean; +begin + Result := inherited; + if Result then + begin + InitializeSelection; + Edit.AutoAdjustSize; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TStringEditLink.CancelEdit : Boolean; +begin + Result := inherited; + if Result then + begin + Edit.ClearLink; + Edit.ClearRefLink; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TStringEditLink.EndEdit : Boolean; +begin + Result := inherited; + if Result then + try + if Edit.Modified then + FTree.Text[FNode, FColumn] := Edit.Text; + Edit.ClearLink; + Edit.ClearRefLink; + except + FStopping := False; + raise; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TStringEditLink.PrepareEdit(Tree : TBaseVirtualTree; Node : PVirtualNode; Column : TColumnIndex) : Boolean; +var + Text : string; +begin + Result := inherited; + if Result then + begin + Edit := TVTEdit.Create(Self); + Edit.Visible := False; + Edit.BorderStyle := bsSingle; + Edit.AutoSize := True; + Edit.Parent := Tree; + //Initial size, font and text of the node. + FTree.GetTextInfo(Node, Column, Edit.Font, FTextBounds, Text); + Edit.Font.Color := clWindowText; + Edit.RecreateWnd; + Edit.AutoSize := False; + Edit.Text := Text; + Edit.BidiMode := FBidiMode; + if Edit.BidiMode <> bdLeftToRight then + ChangeBidiModeAlignment(FAlignment); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TStringEditLink.SetBounds(R : TRect); +//Sets the outer bounds of the edit control and the actual edit area in the control. + +var + lOffset, tOffset, Height : TDimension; + offsets : TVTOffsets; +begin + if not FStopping then + begin + //Check if the provided rect height is smaller than the edit control height. + Height := R.Bottom - R.Top; + if Height < Edit.ClientHeight then + begin + //If the height is smaller than the minimal height we must correct it, otherwise the caret will be invisible. + tOffset := Edit.CalcMinHeight - Height; + if tOffset > 0 then + Inc(R.Bottom, tOffset); + end; + + //Set the edit's bounds but make sure there's a minimum width and the right border does not + //extend beyond the parent's left/right border. + if R.Left < 0 then + R.Left := 0; + if R.Right - R.Left < 30 then + begin + if FAlignment = taRightJustify then + R.Left := R.Right - 30 + else + R.Right := R.Left + 30; + end; + if R.Right > FTree.ClientWidth then + R.Right := FTree.ClientWidth; + Edit.BoundsRect := R; + + //The selected text shall exclude the text margins and be centered vertically. + //We have to take out the two pixel border of the edit control as well as a one pixel "edit border" the + //control leaves around the (selected) text. + R := Edit.ClientRect; + + //If toGridExtensions are turned on, we can fine tune the left margin (or the right margin if RTL is on) + //of the text to exactly match the text in the tree cell. + if (toGridExtensions in TCustomVirtualStringTreeCracker(FTree).TreeOptions.MiscOptions) and + ((FAlignment = taLeftJustify) and (Edit.BidiMode = bdLeftToRight) or (FAlignment = taRightJustify) and (Edit.BidiMode <> bdLeftToRight)) then + begin + //Calculate needed text area offset. + FTree.GetOffsets(FNode, offsets, ofsText, FColumn); + if FColumn = FTree.Header.MainColumn then + begin + if offsets[ofsToggleButton] < 0 then + lOffset := - (offsets[ofsToggleButton] + 2) + else + lOffset := 0; + end + else + lOffset := offsets[ofsText] - offsets[ofsMargin] + 1; + //Apply the offset. + if Edit.BidiMode = bdLeftToRight then + Inc(R.Left, lOffset) + else + Dec(R.Right, lOffset); + end; + + lOffset := IfThen(vsMultiline in FNode.States, 0, 2); + if tsUseThemes in FTree.TreeStates then + Inc(lOffset); + InflateRect(R, - TCustomVirtualStringTreeCracker(FTree).TextMargin + lOffset, lOffset); + if not (vsMultiline in FNode.States) then + begin + tOffset := FTextBounds.Top - Edit.Top; + //Do not apply a negative offset, the cursor will disappear. + if tOffset > 0 then + OffsetRect(R, 0, tOffset); + end; + R.Top := Max( - 1, R.Top); //A value smaller than -1 will prevent the edit cursor from being shown by Windows, see issue #159 + R.Left := Max( - 1, R.Left); + SendMessage(Edit.Handle, EM_SETRECTNP, 0, LPARAM(@R)); + end; +end; + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.Export.pas b/components/virtualtreeview/Source/VirtualTrees.Export.pas index 8ae32cd0f..2bfb985c8 100644 --- a/components/virtualtreeview/Source/VirtualTrees.Export.pas +++ b/components/virtualtreeview/Source/VirtualTrees.Export.pas @@ -19,14 +19,18 @@ procedure ContentToCustom(Tree: TCustomVirtualStringTree; Source: TVSTTextSource implementation uses - Vcl.Graphics, - Vcl.Controls, - Vcl.Forms, System.Classes, System.SysUtils, System.StrUtils, System.Generics.Collections, - System.UITypes; + System.UITypes, + Vcl.Graphics, + Vcl.Controls, + Vcl.Forms, + VirtualTrees.Types, + VirtualTrees.ClipBoard, + VirtualTrees.Header, + VirtualTrees.BaseTree; type TCustomVirtualStringTreeCracker = class(TCustomVirtualStringTree) @@ -67,15 +71,15 @@ function ContentToHTML(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceTyp Value := 48 + (Component shr 4); if Value > $39 then - Inc(Value, 7); + System.Inc(Value, 7); Buffer.Add(AnsiChar(Value)); - Inc(I); + System.Inc(I); Value := 48 + (Component and $F); if Value > $39 then - Inc(Value, 7); + System.Inc(Value, 7); Buffer.Add(AnsiChar(Value)); - Inc(I); + System.Inc(I); WinColor := WinColor shr 8; end; @@ -104,9 +108,9 @@ function ContentToHTML(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceTyp else Buffer.Add(Format('font-size: %dpt; ', [Font.Size])); - Buffer.Add(Format('font-style: %s; ', [IfThen(fsItalic in Font.Style, 'italic', 'normal')])); - Buffer.Add(Format('font-weight: %s; ', [IfThen(fsBold in Font.Style, 'bold', 'normal')])); - Buffer.Add(Format('text-decoration: %s; ', [IfThen(fsUnderline in Font.Style, 'underline', 'none')])); + Buffer.Add(Format('font-style: %s; ', [IfThen(TFontStyle.fsItalic in Font.Style, 'italic', 'normal')])); + Buffer.Add(Format('font-weight: %s; ', [IfThen(TFontStyle.fsBold in Font.Style, 'bold', 'normal')])); + Buffer.Add(Format('text-decoration: %s; ', [IfThen(TFontStyle.fsUnderline in Font.Style, 'underline', 'none')])); Buffer.Add('color: '); WriteColorAsHex(Font.Color); @@ -155,7 +159,7 @@ function ContentToHTML(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceTyp // Add title if adviced so by giving a caption. if Length(Caption) > 0 then AddHeader := AddHeader + 'caption="' + Caption + '"'; - if CrackTree.Borderstyle <> bsNone then + if CrackTree.Borderstyle <> TFormBorderStyle.bsNone then AddHeader := AddHeader + Format(' border="%d" frame=box', [CrackTree.BorderWidth + 1]); Buffer.Add(''); @@ -434,7 +438,7 @@ function ContentToHTML(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceTyp if not RenderColumns then Break; - Inc(I); + System.Inc(I); end; if Assigned(CrackTree.OnAfterNodeExport) then CrackTree.OnAfterNodeExport(CrackTree, etHTML, Run); @@ -535,13 +539,13 @@ function ContentToRTF(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType begin if Length(Text) > 0 then begin - UseUnderline := fsUnderline in Font.Style; + UseUnderline := TFontStyle.fsUnderline in Font.Style; if UseUnderline then Buffer.Add('\ul'); - UseItalic := fsItalic in Font.Style; + UseItalic := TFontStyle.fsItalic in Font.Style; if UseItalic then Buffer.Add('\i'); - UseBold := fsBold in Font.Style; + UseBold := TFontStyle.fsBold in Font.Style; if UseBold then Buffer.Add('\b'); SelectFont(Font.Name); @@ -630,7 +634,7 @@ function ContentToRTF(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType begin for I := 0 to High(Columns) do begin - Inc(J, Columns[I].Width); + System.Inc(J, Columns[I].Width); // This value must be expressed in twips (1 inch = 1440 twips). Twips := Round(1440 * J / Screen.PixelsPerInch); Buffer.Add('\cellx'); @@ -725,7 +729,7 @@ function ContentToRTF(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType end; // Call back the application to know about font customization. - CrackTree.Canvas.Font := CrackTree.Font; + CrackTree.Canvas.Font.Assign(CrackTree.Font); CrackTree.FFontChanged := False; CrackTree.DoPaintText(Run, CrackTree.Canvas, Index, ttNormal); @@ -764,7 +768,7 @@ function ContentToRTF(Tree: TCustomVirtualStringTree; Source: TVSTTextSourceType if not RenderColumns then Break; - Inc(I); + System.Inc(I); end; Buffer.Add('\row'); Buffer.AddNewLine; diff --git a/components/virtualtreeview/Source/VirtualTrees.FMX.pas b/components/virtualtreeview/Source/VirtualTrees.FMX.pas new file mode 100644 index 000000000..fa2ad407a --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.FMX.pas @@ -0,0 +1,1781 @@ +unit VirtualTrees.FMX; + +{$SCOPEDENUMS ON} + +{***********************************************************} +{ Project : VirtualTrees } +{ } +{ author : Karol Bieniaszewski } +{ year : 2018 } +{ contibutors : } +{***********************************************************} + +interface +uses + System.Classes + , System.UITypes + , System.Types + , System.ImageList + , System.Math.Vectors + , FMX.ImgList + , FMX.Graphics + , FMX.Controls + , FMX.Types + , FMX.StdCtrls; + +//-------- type aliasing ------------------------------------------------------------------------------------------------------------------- + +type + TRect = System.Types.TRectF; + PRect = System.Types.PRectF; + TPoint = System.Types.TPointF; + PPoint = System.Types.PPointF; + PSize = System.Types.PSizeF; + TSize = System.Types.TSizeF; + TColor = System.UITypes.TAlphaColor; + PAnsiChar = System.MarshaledAString; + UINT = LongWord; + PUINT = ^UINT; + TCustomControl = TControl; //Alias for VCL compatibility as on FMX there is not TCustomControl + +//------- color aliasing ------------------------------------------------------------------------------------------------------------------- + +const + clBtnFace = TAlphaColor($FFF0F0F0); //TAlphaColorRec.Gray; + clBtnText = TAlphaColorRec.Black; + clBtnHighlight = TAlphaColorRec.DkGray; + clBtnShadow = TAlphaColorRec.Darkgray; + clHighlight = TAlphaColorRec.Lightblue; + clWindow = TAlphaColorRec.White; + clWindowText = TAlphaColorRec.Black; + clHighlightText = TAlphaColorRec.White; + clWhite = TAlphaColorRec.White; + clSilver = TAlphaColorRec.Silver; + clGray = TAlphaColorRec.Gray; + clBlack = TAlphaColorRec.Black; + clGreen = TAlphaColorRec.Green; + clBlue = TAlphaColorRec.Blue; + clGrayText = TAlphaColorRec.DkGray; + clInactiveCaption = TAlphaColorRec.Darkblue; //TODO: color + clInactiveCaptionText = TAlphaColorRec.Yellow; //TODO: color + clDkGray = TAlphaColorRec.DkGray; + + +//------- needed for migration ------------------------------------------------------------------------------------------------------------- + +const + { 3D border styles } + BDR_RAISEDOUTER = 1; + BDR_SUNKENOUTER = 2; + BDR_RAISEDINNER = 4; + BDR_SUNKENINNER = 8; + + BDR_OUTER = 3; + BDR_INNER = 12; + BDR_RAISED = 5; + BDR_SUNKEN = 10; + + EDGE_RAISED = (BDR_RAISEDOUTER or BDR_RAISEDINNER); + EDGE_SUNKEN = (BDR_SUNKENOUTER or BDR_SUNKENINNER); + EDGE_ETCHED = (BDR_SUNKENOUTER or BDR_RAISEDINNER); + EDGE_BUMP = (BDR_RAISEDOUTER or BDR_SUNKENINNER); + + ETO_OPAQUE = 2; + ETO_CLIPPED = 4; + ETO_RTLREADING = $80; + + RTLFlag: array[Boolean] of Integer = (0, ETO_RTLREADING); + + { Border flags } + BF_LEFT = 1; + BF_TOP = 2; + BF_RIGHT = 4; + BF_BOTTOM = 8; + + BF_TOPLEFT = (BF_TOP or BF_LEFT); + BF_TOPRIGHT = (BF_TOP or BF_RIGHT); + BF_BOTTOMLEFT = (BF_BOTTOM or BF_LEFT); + BF_BOTTOMRIGHT = (BF_BOTTOM or BF_RIGHT); + BF_RECT = (BF_LEFT or BF_TOP or BF_RIGHT or BF_BOTTOM); + + BF_MIDDLE = $800; { Fill in the middle } + BF_SOFT = $1000; { For softer buttons } + BF_ADJUST = $2000; { Calculate the space left over } + BF_FLAT = $4000; { For flat rather than 3D borders } + BF_MONO = $8000; { For monochrome borders } + + { DrawText() Format Flags } + DT_TOP = 0; + DT_LEFT = 0; + DT_CENTER = 1; + DT_RIGHT = 2; + DT_VCENTER = 4; + DT_BOTTOM = 8; + DT_WORDBREAK = $10; + DT_SINGLELINE = $20; + DT_EXPANDTABS = $40; + DT_TABSTOP = $80; + DT_NOCLIP = $100; + DT_EXTERNALLEADING = $200; + DT_CALCRECT = $400; + DT_NOPREFIX = $800; + DT_INTERNAL = $1000; + + + DT_EDITCONTROL = $2000; + DT_PATH_ELLIPSIS = $4000; + DT_END_ELLIPSIS = $8000; + DT_MODIFYSTRING = $10000; + DT_RTLREADING = $20000; + DT_WORD_ELLIPSIS = $40000; + DT_NOFULLWIDTHCHARBREAK = $0080000; + DT_HIDEPREFIX = $00100000; + DT_PREFIXONLY = $00200000; + + MAXDWORD = DWORD($FFFFFFFF); + WHEEL_DELTA = 120; { Value for rolling one detent } + WHEEL_PAGESCROLL = MAXDWORD; { Scroll one page } + + { WM_SIZE message wParam values } + SIZE_RESTORED = 0; + SIZE_MINIMIZED = 1; + SIZE_MAXIMIZED = 2; + SIZE_MAXSHOW = 3; + SIZE_MAXHIDE = 4; + + { Scroll Bar Constants } + SB_HORZ = 0; + SB_VERT = 1; + SB_CTL = 2; + SB_BOTH = 3; + + SIF_RANGE = 1; + SIF_PAGE = 2; + SIF_POS = 4; + SIF_DISABLENOSCROLL = 8; + SIF_TRACKPOS = $10; + SIF_ALL = (SIF_RANGE or SIF_PAGE or SIF_POS or SIF_TRACKPOS); + + { Scroll Bar Commands } + SB_LINEUP = 0; + SB_LINELEFT = 0; + SB_LINEDOWN = 1; + SB_LINERIGHT = 1; + SB_PAGEUP = 2; + SB_PAGELEFT = 2; + SB_PAGEDOWN = 3; + SB_PAGERIGHT = 3; + SB_THUMBPOSITION = 4; + SB_THUMBTRACK = 5; + SB_TOP = 6; + SB_LEFT = 6; + SB_BOTTOM = 7; + SB_RIGHT = 7; + SB_ENDSCROLL = 8; + + { RedrawWindow() flags } + RDW_INVALIDATE = 1; + RDW_INTERNALPAINT = 2; + RDW_ERASE = 4; + RDW_VALIDATE = 8; + RDW_NOINTERNALPAINT = $10; + RDW_NOERASE = $20; + RDW_NOCHILDREN = $40; + RDW_ALLCHILDREN = $80; + RDW_UPDATENOW = $100; + RDW_ERASENOW = $200; + RDW_FRAME = $400; + RDW_NOFRAME = $800; + + { GetSystemMetrics() codes } + SM_CXVSCROLL = 2; + SM_CYHSCROLL = 3; +var + // Clipboard format IDs used in OLE drag'n drop and clipboard transfers. + CF_VIRTUALTREE, + CF_VTREFERENCE, // A reference to a virtual tree + CF_VTHEADERREFERENCE, // drapg and drop of column headers + CF_VRTF, + CF_VRTFNOOBJS, // Unfortunately CF_RTF* is already defined as being + // registration strings so I have to use different identifiers. + CF_HTML, + CF_CSV: Word; + +type + tagSCROLLINFO = record + cbSize: UINT; + fMask: UINT; + nMin: Single; + nMax: Single; + nPage: Single; + nPos: Single; + nTrackPos: Single; + end; + PScrollInfo = ^TScrollInfo; + TScrollInfo = tagSCROLLINFO; + SCROLLINFO = tagSCROLLINFO; + + TBorderWidth = Single; + TBevelCut = (bvNone, bvLowered, bvRaised, bvSpace); + TBevelEdge = (beLeft, beTop, beRight, beBottom); + TBevelEdges = set of TBevelEdge; + TBevelKind = (bkNone, bkTile, bkSoft, bkFlat); + TBevelWidth = 1..MaxInt; + + TFormBorderStyle = (bsNone, bsSingle, bsSizeable, bsDialog, bsToolWindow, bsSizeToolWin); + TBorderStyle = TFormBorderStyle.bsNone..TFormBorderStyle.bsSingle; + + + + TChangeLink = class(TImageLink) + private + function GetSender: TCustomImageList; inline; + procedure SetSender(const Value: TCustomImageList); inline; + public + constructor Create; override; + property Sender: TCustomImageList read GetSender write SetSender; + end; + + INT_PTR = Integer; //do not change on Int64 //System.IntPtr; // NativeInt; + {$EXTERNALSYM INT_PTR} + UINT_PTR = Cardinal; //do not change on Int64 //System.UIntPtr; // NativeUInt; + + WPARAM = UINT_PTR; + LPARAM = INT_PTR; + LRESULT = INT_PTR; + + TDWordFiller = record + {$IFDEF CPUX64} + Filler: array[1..4] of Byte; // Pad DWORD to make it 8 bytes (4+4) [x64 only] + {$ENDIF} + end; + +//--------- Windows messages simulations --------------------------------------------------------------------------------------------------- + +const + WM_APP = $8000; + WM_MOUSEFIRST = $0200; + WM_MOUSEMOVE = $0200; + WM_LBUTTONDOWN = $0201; + WM_LBUTTONUP = $0202; + WM_LBUTTONDBLCLK = $0203; + WM_RBUTTONDOWN = $0204; + WM_RBUTTONUP = $0205; + WM_RBUTTONDBLCLK = $0206; + WM_MBUTTONDOWN = $0207; + WM_MBUTTONUP = $0208; + WM_MBUTTONDBLCLK = $0209; + WM_MOUSEWHEEL = $020A; + WM_SIZE = $0005; + WM_NCMBUTTONDOWN = $00A7; + WM_NCMBUTTONUP = $00A8; + WM_NCMBUTTONDBLCLK = $00A9; + WM_NCLBUTTONDBLCLK = $00A3; + WM_NCRBUTTONDOWN = $00A4; + WM_NCRBUTTONUP = $00A5; + WM_NCRBUTTONDBLCLK = $00A6; + WM_NCLBUTTONDOWN = $00A1; + WM_NCLBUTTONUP = $00A2; + WM_NCMOUSEMOVE = $00A0; + WM_KEYDOWN = $0100; + WM_KEYUP = $0101; + WM_SETFOCUS = $0007; + WM_KILLFOCUS = $0008; + WM_SETCURSOR = $0020; + WM_HSCROLL = $0114; + WM_VSCROLL = $0115; + WM_CHANGESTATE = WM_APP + 32; + + CM_BASE = $B000; +{$IF DEFINED(CLR)} + CM_CLROFFSET = $100; +{$ELSE} + CM_CLROFFSET = $0; // Only applicable in CLR +{$ENDIF} + CM_ACTIVATE = CM_BASE + 0; + CM_DEACTIVATE = CM_BASE + 1; + CM_GOTFOCUS = CM_BASE + 2; + CM_LOSTFOCUS = CM_BASE + 3; + CM_CANCELMODE = CM_BASE + CM_CLROFFSET + 4; + CM_DIALOGKEY = CM_BASE + 5; + CM_DIALOGCHAR = CM_BASE + 6; +{$IF NOT DEFINED(CLR)} + CM_FOCUSCHANGED = CM_BASE + 7; +{$ENDIF} + CM_PARENTFONTCHANGED = CM_BASE + CM_CLROFFSET + 8; + CM_PARENTCOLORCHANGED = CM_BASE + 9; + CM_BIDIMODECHANGED = CM_BASE + 60; + CM_PARENTBIDIMODECHANGED = CM_BASE + 61; + CM_MOUSEWHEEL = CM_BASE + 67; + + VK_ESCAPE = 27; + +type + PMessage = ^TMessage; + TMessage = record + Msg: Cardinal; //4 + tmp: Integer; //4 + case Integer of + 0: ( + WParam: WPARAM; //4 + LParam: LPARAM; //4 + Result: LRESULT //4 + ); //= 12 + 4 = 16 + 1: ( + WParamLo: Word; //2 + WParamHi: Word; //2 + //WParamFiller: TDWordFiller; + LParamLo: Word; //2 + LParamHi: Word; //2 + //LParamFiller: TDWordFiller; + ResultLo: Word; //2 + ResultHi: Word; //2 + //=12 + 8 = 20 + ); + end; + + TWMMouse = record + Msg: Cardinal; //4 + Keys: Longint; //TShiftState; //4 + //KeysFiller: TDWordFiller; + case Integer of + 0: ( + XPos: Single; //4 + YPos: Single; //4 + Result: LRESULT; //4 + ); + 1: ( + Pos: TPoint; //8 + ResultLo: Word; //2 + ResultHi: Word; //2 + ); //=12 + 8=20 + end; + + TWMMouseMove = TWMMouse; + + TWMNCHitTest = record + Msg: Cardinal; + //MsgFiller: TDWordFiller; + Unused: WPARAM; + case Integer of + 0: ( + XPos: Single; + YPos: Single; + //XYPosFiller: TDWordFiller + ); + 1: ( + Pos: TPoint; + //PosFiller: TDWordFiller; + Result: LRESULT); + end; + + TWMNCHitMessage = record + Msg: Cardinal; //4 + //MsgFiller: TDWordFiller; + HitTest: Longint; //4 + //HitTestFiller: TDWordFiller; + XCursor: Single; //4 + YCursor: Single; //4 + //XYCursorFiller: TDWordFiller; + Result: LRESULT; //4 + end; //=20 + + TWMNCLButtonDblClk = TWMNCHitMessage; + TWMNCLButtonDown = TWMNCHitMessage; + TWMNCLButtonUp = TWMNCHitMessage; + TWMNCMButtonDblClk = TWMNCHitMessage; + TWMNCMButtonDown = TWMNCHitMessage; + TWMNCMButtonUp = TWMNCHitMessage; + TWMNCMouseMove = TWMNCHitMessage; + TWMNCRButtonDblClk = TWMNCHitMessage; + TWMNCRButtonDown = TWMNCHitMessage; + TWMNCRButtonUp = TWMNCHitMessage; + + TWMLButtonDblClk = TWMMouse; + TWMLButtonDown = TWMMouse; + TWMLButtonUp = TWMMouse; + TWMMButtonDblClk = TWMMouse; + TWMMButtonDown = TWMMouse; + TWMMButtonUp = TWMMouse; + + + TWMKey = record + Msg: Cardinal; //4 + tmp: Integer; //4 + CharCode: Word; //4 + //Unused: Word; //2 + KeyData: Longint; //4 + Result: LRESULT; //4 + end; //=20 + + TWMKeyDown = TWMKey; + TWMKeyUp = TWMKey; + + TWMSize = record + Msg: Cardinal; //4 + //MsgFiller: TDWordFiller; + SizeType: WPARAM; { SIZE_MAXIMIZED, SIZE_MINIMIZED, SIZE_RESTORED, //4 + SIZE_MAXHIDE, SIZE_MAXSHOW } + Width: Single; //4 + Height: Single; //4 + //WidthHeightFiller: TDWordFiller; + Result: LRESULT; //4 + end; //=20 + + TWMScroll = record + Msg: Cardinal; //4 + //MsgFiller: TDWordFiller; + ScrollCode: {Smallint}Integer; { SB_xxxx } //4 + Pos: Single; //4 + //ScrollCodePosFiller: TDWordFiller; + ScrollBar: Integer; //4 nBar + Result: LRESULT; //4 + end; //=20 + + TWMHScroll = TWMScroll; + TWMVScroll = TWMScroll; + + TCMMouseWheel = record + Msg: Cardinal; //4 + //MsgFiller: TDWordFiller; + ShiftState: TShiftState; //2 + WheelDelta: SmallInt; //2 + //ShiftStateWheel: TDWordFiller; + case Integer of + 0: ( + XPos: Single; //4 + YPos: Single; //4 + //XYPos: TDWordFiller + ); //=24! + 1: ( + Pos: TPoint; //8 + //PosFiller: TDWordFiller; + Result: LRESULT //4 + ); //=28! + end; + + +procedure FillTWMMouse(Var MM: TWMMouse; Button: TMouseButton; Shift: TShiftState; X: Single; Y: Single; IsNC: Boolean; IsUp: Boolean); + +//--------- Text metrics ------------------------------------------------------------------------------------------------------------------- +type + TTextMetric = record + tmHeight: Single; //The height (ascent + descent) of characters. + tmAscent: Single; //The ascent (units above the base line) of characters. + tmDescent: Single; //The descent (units below the base line) of characters. + tmInternalLeading: Single; //The amount of leading (space) inside the bounds set by the tmHeight member. Accent marks and other diacritical characters may occur in this area. The designer may set this member to zero + tmExternalLeading: Single; //The amount of extra leading (space) that the application adds between rows. Since this area is outside the font, it contains no marks and is not altered by text output calls in either OPAQUE or TRANSPARENT mode. The designer may set this member to zero. + tmAveCharWidth: Single; //The average width of characters in the font (generally defined as the width of the letter x ). This value does not include the overhang required for bold or italic characters. + tmMaxCharWidth: Single; //The width of the widest character in the font. + tmWeight: Single; //The weight of the font. + tmOverhang: Single; + tmDigitizedAspectX: Single; //The horizontal aspect of the device for which the font was designed. + tmDigitizedAspectY: Single; //The vertical aspect of the device for which the font was designed. The ratio of the tmDigitizedAspectX and tmDigitizedAspectY members is the aspect ratio of the device for which the font was designed. + tmFirstChar: WideChar; //The value of the first character defined in the font. + tmLastChar: WideChar; //The value of the last character defined in the font. + tmDefaultChar: WideChar; //The value of the character to be substituted for characters not in the font. + tmBreakChar: WideChar; //The value of the character that will be used to define word breaks for text justification. + tmItalic: Byte; //Specifies an italic font if it is nonzero. + tmUnderlined: Byte; //Specifies an underlined font if it is nonzero. + tmStruckOut: Byte; //A strikeout font if it is nonzero. + tmPitchAndFamily: Byte; //Specifies information about the pitch, the technology, and the family of a physical font. TMPF_FIXED_PITCH, TMPF_VECTOR, TMPF_TRUETYPE, TMPF_DEVICE + tmCharSet: Byte; //The character set of the font. The character set can be one of the following values. ANSI_CHARSET, GREEK_CHARSET.... + end; + procedure GetTextMetrics(ACanvas: TCanvas; var TM: TTextMetric); + +//-------- function aliassing -------------------------------------------------------------------------------------------------------------- + +function Rect(ALeft, ATop, ARight, ABottom: Single): TRect; overload; inline; +function Rect(const ATopLeft, ABottomRight: TPoint): TRect; overload; inline; +function Point(AX, AY: Single): TPoint; overload; inline; + +procedure Inc(Var V: Single; OIle: Single=1.0); overload; +procedure Dec(Var V: Single; OIle: Single=1.0); overload; +function MulDiv(const A, B, C: Single): Single; overload; +procedure FillMemory(Destination: Pointer; Length: NativeUInt; Fill: Byte); +procedure ZeroMemory(Destination: Pointer; Length: NativeUInt); +procedure MoveMemory(Destination: Pointer; Source: Pointer; Length: NativeUInt); +procedure CopyMemory(Destination: Pointer; Source: Pointer; Length: NativeUInt); + +procedure DrawTextW(ACanvas: TCanvas; CaptionText: String; Len: Integer; Var Bounds: TRect; DrawFormat: Cardinal{this is windows format - must be converted to FMX}); +procedure GetTextExtentPoint32W(ACanvas: TCanvas; CaptionText: String; Len: Integer; Var Size: TSize); +procedure DrawEdge(Canvas: TCanvas; R: TRect; edge, grfFlags: Cardinal); + +type + THighQualityBitmap = class(TBitmap) + public + constructor Create; override; + end; + +//fill system images +procedure FillSystemCheckImages(Parent: TFmxObject; List: TImageList); + +type + TCanvasHelper = class helper for TCanvas + private + function GetBrush: TBrush; inline; + function GetPen: TStrokeBrush; inline; + public + property Brush: TBrush read GetBrush; + property Pen: TStrokeBrush read GetPen; + procedure FillRect(const ARect: TRectF); overload; inline; + procedure DrawRect(const ARect: TRectF); overload; inline; + procedure DrawFocusRect(const AFocusRect: TRect); + procedure FrameRect(const AFocusRect: TRect); + procedure RoundRect(X1, Y1, X2, Y2: Single; const XRadius, YRadius: Single); overload; + procedure RoundRect(const Rect: TRect; const XRadius, YRadius: Single); overload; + procedure Polygon(const Points: TPolygon); + procedure Draw(const X, Y: Single; const Bitmap: TBitmap); + end; + + TFontHelper = class helper for TFont + private + function GetOnChange: TNotifyEvent; + procedure SetOnChange(const Value: TNotifyEvent); + public + property OnChange: TNotifyEvent read GetOnChange write SetOnChange; + end; + +{ Draws a solid triangular arrow that can point in any TScrollDirection } + +type + TScrollDirection = (sdLeft, sdRight, sdUp, sdDown); + TArrowType = (atSolid, atArrows); + +procedure DrawArrow(ACanvas: TCanvas; Direction: TScrollDirection; Location: TPoint; Size: Single); + +procedure ChangeBiDiModeAlignment(var Alignment: TAlignment); + +procedure OleUninitialize(); + +function timeGetTime: Int64; + +implementation +uses + System.SysUtils + , FMX.TextLayout + , FMX.MultiResBitmap + , FMX.Objects + , FMX.Effects + , VirtualTrees.Utils + ; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure DrawArrow(ACanvas: TCanvas; Direction: TScrollDirection; Location: TPoint; Size: Single); +begin + //TODO: DrawArrow implementation +end; + +//---------------------------------------------------------------------------------------------------------------------- + +{ TCanvasHelper } + +procedure TCanvasHelper.Draw(const X, Y: Single; const Bitmap: TBitmap); +begin + DrawBitmap(Bitmap + , Rect(0, 0, Bitmap.Width, Bitmap.Height) + , Rect(X, Y, X+Bitmap.Width, Y+ Bitmap.Height) + , 1.0 + ); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCanvasHelper.DrawFocusRect(const AFocusRect: TRect); +begin + DrawDashRect(AFocusRect, 0, 0, AllCorners, 1.0{?}, $A0909090); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCanvasHelper.DrawRect(const ARect: TRectF); +begin + DrawRect(ARect, 0, 0, [], 1.0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCanvasHelper.FillRect(const ARect: TRectF); +begin + FillRect(ARect, 0, 0, [], 1.0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCanvasHelper.FrameRect(const AFocusRect: TRect); +begin + DrawRect(AFocusRect); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCanvasHelper.GetBrush: TBrush; +begin + Result:= Fill; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCanvasHelper.GetPen: TStrokeBrush; +begin + Result:= Stroke; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCanvasHelper.Polygon(const Points: TPolygon); +begin + DrawPolygon(Points, 1.0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCanvasHelper.RoundRect(const Rect: TRect; const XRadius, YRadius: Single); +begin + DrawRect(Rect, XRadius, YRadius, allCorners, 1.0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCanvasHelper.RoundRect(X1, Y1, X2, Y2: Single; const XRadius, YRadius: Single); +begin + RoundRect(Rect(X1, Y1, X2, Y2), XRadius, YRadius); +end; + + +//---------------------------------------------------------------------------------------------------------------------- + +type + TImageListHelper = class helper for TImageList + function Add(aBitmap: TBitmap): integer; + end; + +function TImageListHelper.Add(aBitmap: TBitmap): integer; +const + SCALE = 1; +var + vSource: TCustomSourceItem; + vBitmapItem: TCustomBitmapItem; + vDest: TCustomDestinationItem; + vLayer: TLayer; +begin + Result := -1; + if (aBitmap.Width = 0) or (aBitmap.Height = 0) then exit; + + // add source bitmap + vSource := Source.Add; + vSource.MultiResBitmap.TransparentColor := TColorRec.Fuchsia; + vSource.MultiResBitmap.SizeKind := TSizeKind.Source; + vSource.MultiResBitmap.Width := Round(aBitmap.Width / SCALE); + vSource.MultiResBitmap.Height := Round(aBitmap.Height / SCALE); + vBitmapItem := vSource.MultiResBitmap.ItemByScale(SCALE, True, True); + if vBitmapItem = nil then + begin + vBitmapItem := vSource.MultiResBitmap.Add; + vBitmapItem.Scale := Scale; + end; + vBitmapItem.Bitmap.Assign(aBitmap); + + vDest := Destination.Add; + vLayer := vDest.Layers.Add; + vLayer.SourceRect.Rect := TRectF.Create(TPoint.Zero, vSource.MultiResBitmap.Width, + vSource.MultiResBitmap.Height); + vLayer.Name := vSource.Name; + Result := vDest.Index; +end; + +//---------------------------------------------------------------------------------------------------------------------- +//https://stackoverflow.com/questions/22813461/is-there-an-equivalent-to-floodfill-in-fmx-for-a-tbitmap +procedure Bitmap_FloodFill(fBitmap: TBitmap; StartX,StartY : Integer; FillColor: TAlphaColor); +var + fBitmapData : TBitmapData; + X, Y : Integer; + ReplaceColor : TAlphaColor; + Stack : Array of System.Types.TPoint; + fHeight : Integer; + fWidth : Integer; + + procedure PutInStack(X, Y: Integer); + begin + SetLength(Stack, Length(Stack)+1); + Stack[Length(Stack)-1] := Point(X, Y); + end; + + procedure GetFromStack(var X, Y: Integer); + begin + X := Stack[Length(Stack)-1].X; + Y := Stack[Length(Stack)-1].Y; + SetLength(Stack, Length(Stack)-1); + end; + +begin + X := StartX; + Y := StartY; + fHeight := fBitmap.Height; + fWidth := fBitmap.Width; + if (X >= fWidth) or (Y >= fHeight) then Exit; + + if fBitmap.Map(TMapAccess.ReadWrite,fBitmapData) then + try + ReplaceColor := fBitmapData.GetPixel(X,Y); + if ReplaceColor <> FillColor then + begin + PutInStack(X,Y); + while Length(Stack) > 0 do + begin + GetFromStack(X,Y); + while (X > 0) and (fBitmapData.GetPixel(X-1, Y) = ReplaceColor) do System.Dec(X); + while (X < fWidth) and (fBitmapData.GetPixel(X , Y) = ReplaceColor) do + begin + if Y > 0 then If fBitmapData.GetPixel(X, Y-1) = ReplaceColor then PutInStack(X, Y-1); + if Y+1 < fHeight then If fBitmapData.GetPixel(X, Y+1) = ReplaceColor then PutInStack(X, Y+1); + fBitmapData.SetPixel(X,Y,FillColor); + System.Inc(X); + end; + end; + end; + finally + fBitmap.Canvas.Bitmap.Unmap(fBitmapData); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +{ + ckEmpty = 0; // an empty image used as place holder + // radio buttons + ckRadioUncheckedNormal = 1; + ckRadioUncheckedHot = 2; + ckRadioUncheckedPressed = 3; + ckRadioUncheckedDisabled = 4; + ckRadioCheckedNormal = 5; + ckRadioCheckedHot = 6; + ckRadioCheckedPressed = 7; + ckRadioCheckedDisabled = 8; + // check boxes + ckCheckUncheckedNormal = 9; + ckCheckUncheckedHot = 10; + ckCheckUncheckedPressed = 11; + ckCheckUncheckedDisabled = 12; + ckCheckCheckedNormal = 13; + ckCheckCheckedHot = 14; + ckCheckCheckedPressed = 15; + ckCheckCheckedDisabled = 16; + ckCheckMixedNormal = 17; + ckCheckMixedHot = 18; + ckCheckMixedPressed = 19; + ckCheckMixedDisabled = 20; + // simple button + ckButtonNormal = 21; //??? + ckButtonHot = 22; //??? + ckButtonPressed = 23; //??? + ckButtonDisabled = 24; //??? +} +procedure FillSystemCheckImages(Parent: TFmxObject; List: TImageList); +Var cb: TCheckBox; + rb: TRadioButton; + BMP: TBitmap; + eff: TInnerGlowEffect; + procedure AddCtrlBmp(c: TControl); + Var tmpBMP: TBitmap; + begin + tmpBMP:= c.MakeScreenshot; + try + BMP.SetSize(tmpBMP.Height, tmpBMP.Height); + BMP.Clear(TAlphaColorRec.Null); //this somehow can sometimes clear BeginSceneCount and must be before BeginScene + if BMP.Canvas.BeginScene() then + begin + try + BMP.Canvas.DrawBitmap( + tmpBMP + , Rect(2, 2, BMP.Width, BMP.Height) + , Rect(0, 0, BMP.Width-2, BMP.Height-2) + , 1.0 + , false + ); + finally + BMP.Canvas.EndScene; + end; + end; + finally + FreeAndNil(tmpBMP); + end; + end; +begin + BMP:= TBitmap.Create; + try + BMP.SetSize(16, 16); + BMP.Clear(TAlphaColorRec.Null); + List.Add(BMP); //ckEmpty + + + rb:= TRadioButton.Create(Parent); + try + rb.Parent:= Parent; + rb.Text:= ' '; + + eff:= TInnerGlowEffect.Create(rb); //auto free + eff.Parent:= rb; + eff.GlowColor:= TAlphaColorRec.Teal; + eff.Softness:= 8; + eff.Opacity:= 0.7; + eff.Enabled:= false; + + //------------------IsUnChecked-------------------------- + + rb.IsChecked:= false; + eff.Enabled:= false; + AddCtrlBmp(rb); + List.Add(BMP); //ckRadioUncheckedNormal + eff.Enabled:= false; + + + AddCtrlBmp(rb); + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Lightyellow; + List.Add(BMP); //ckRadioUncheckedHot + eff.Enabled:= false; + + + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Lightblue; + AddCtrlBmp(rb); + List.Add(BMP); //ckRadioUncheckedPressed + eff.Enabled:= false; + + + rb.Enabled:= false; + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Gray; + AddCtrlBmp(rb); + List.Add(BMP); //ckRadioUncheckedDisabled + eff.Enabled:= false; + + //------------------IsChecked--------------------------- + + rb.IsChecked:= true; + + //rb.IsPressed:= false; + rb.Enabled:= true; + eff.Enabled:= false; + AddCtrlBmp(rb); + List.Add(BMP); //ckRadioCheckedNormal + eff.Enabled:= false; + + + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Lightyellow; + AddCtrlBmp(rb); + List.Add(BMP); //ckRadioCheckedHot + eff.Enabled:= false; + + + rb.Enabled:= true; + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Lightblue; + AddCtrlBmp(rb); + List.Add(BMP); //ckRadioCheckedPressed + eff.Enabled:= false; + + + rb.Enabled:= false; + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Gray; + AddCtrlBmp(rb); + List.Add(BMP); //ckRadioCheckedDisabled + eff.Enabled:= false; + finally + FreeAndNil(rb); + end; + + cb:= TCheckBox.Create(Parent); + try + cb.Parent:= Parent; + cb.Text:= ' '; + + eff:= TInnerGlowEffect.Create(cb); //auto free + eff.Parent:= cb; + eff.GlowColor:= TAlphaColorRec.Teal; + eff.Softness:= 8; + eff.Opacity:= 0.7; + eff.Enabled:= false; + + //------------------IsUnChecked-------------------------- + + cb.IsChecked:= false; + eff.Enabled:= false; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckUncheckedNormal + eff.Enabled:= false; + + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Lightyellow; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckUncheckedHot + eff.Enabled:= false; + + + //cb.IsPressed:= true; + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Lightblue; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckUncheckedPressed + eff.Enabled:= false; + + + //cb.IsPressed:= false; + cb.Enabled:= false; + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Gray; + AddCtrlBmp(cb); + eff.Enabled:= false; + List.Add(BMP); //ckCheckUncheckedDisabled + + //------------------IsChecked--------------------------- + + cb.IsChecked:= true; + + cb.Enabled:= true; + eff.Enabled:= false; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckCheckedNormal + eff.Enabled:= false; + + + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Lightyellow; + eff.Opacity:= 0.3; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckCheckedHot + eff.Opacity:= 0.7; + eff.Enabled:= false; + eff.Enabled:= false; + + + cb.Enabled:= true; + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Lightblue; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckCheckedPressed + eff.Enabled:= false; + + + cb.Enabled:= false; + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Gray; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckCheckedDisabled + eff.Enabled:= false; + //------------------Mixed--------------------------- + + //how to support mixed style? + //maybe draw unchecked and fill in the center of bitmap??? + //i use ~teal for fill + //changed to InnerGlowEffect + + cb.IsChecked:= true; + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Green; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckMixedNormal + eff.Enabled:= false; + + + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Lightyellow; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckMixedHot + eff.Enabled:= false; + + + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Lightblue; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckMixedPressed + eff.Enabled:= false; + + + cb.Enabled:= false; + eff.Enabled:= true; + eff.GlowColor:= TAlphaColorRec.Gray; + AddCtrlBmp(cb); + List.Add(BMP); //ckCheckMixedDisabled + eff.Enabled:= false; + + finally + FreeAndNil(cb); + end; + eff.Enabled:= false; + eff.Parent:= nil; + + finally + FreeAndNil(BMP); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure FillTWMMouse(Var MM: TWMMouse; Button: TMouseButton; Shift: TShiftState; X: Single; Y: Single; IsNC: Boolean; IsUp: Boolean); +begin + MM.Msg:= 0; + if ssDouble in Shift then + begin + if ssLeft in Shift then + begin + if IsNC then + MM.Msg:= WM_NCLBUTTONDBLCLK else + MM.Msg:= WM_LBUTTONDBLCLK; + end else + if ssRight in Shift then + begin + if IsNC then + MM.Msg:= WM_NCRBUTTONDBLCLK else + MM.Msg:= WM_RBUTTONDBLCLK; + end else + if ssMiddle in Shift then + begin + if IsNC then + MM.Msg:= WM_NCMBUTTONDBLCLK else + MM.Msg:= WM_MBUTTONDBLCLK; + end; + end else + begin + if (ssLeft in Shift) or (Button=TMouseButton.mbLeft) then + begin + if IsUp then + begin + if IsNC then + MM.Msg:= WM_NCLBUTTONUP else + MM.Msg:= WM_LBUTTONUP; + end else + begin + if IsNC then + MM.Msg:= WM_NCLBUTTONDOWN else + MM.Msg:= WM_LBUTTONDOWN; + end; + end else + if (ssRight in Shift) or (Button=TMouseButton.mbRight) then + begin + if IsUp then + begin + if IsNC then + MM.Msg:= WM_NCRBUTTONUP else + MM.Msg:= WM_RBUTTONUP; + end else + begin + if IsNC then + MM.Msg:= WM_NCRBUTTONDOWN else + MM.Msg:= WM_RBUTTONDOWN; + end; + + end else + if (ssMiddle in Shift) or (Button=TMouseButton.mbMiddle) then + begin + if IsUp then + begin + if IsNC then + MM.Msg:= WM_NCMBUTTONUP else + MM.Msg:= WM_MBUTTONUP; + end else + begin + if IsNC then + MM.Msg:= WM_NCMBUTTONDOWN else + MM.Msg:= WM_MBUTTONDOWN; + end; + end; + end; + + MM.XPos:= X; + MM.YPos:= Y; + MM.Keys:= LongInt(Word(Shift)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure DrawTextW(ACanvas: TCanvas; CaptionText: String; Len: Integer; Var Bounds: TRect; DrawFormat: Cardinal{this is windows format - must be converted to FMX}); +Var + hAlign: TTextAlign; + vAlign: TTextAlign; + Flags: TFillTextFlags; +begin + //TTextLayout. render + //TODO: DrawFormat: Cardinal{this is windows format - must be converted to FMX} + + hAlign:= TTextAlign.Leading; + if DrawFormat and DT_CENTER<>0 then + hAlign:= TTextAlign.Center; + if DrawFormat and DT_RIGHT<>0 then + hAlign:= TTextAlign.Trailing; + + + vAlign:= TTextAlign.Center; + if DrawFormat and DT_VCENTER<>0 then + vAlign:= TTextAlign.Center; + if DrawFormat and DT_BOTTOM<>0 then + vAlign:= TTextAlign.Trailing; + + Flags:= []; + + if DrawFormat and DT_RTLREADING<>0 then + Flags:= Flags + [TFillTextFlag.RightToLeft]; + + ACanvas.FillText(Bounds, CaptionText, false, 1.0, Flags, hAlign, vAlign); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure DrawEdge(Canvas: TCanvas; R: TRect; edge, grfFlags: Cardinal); +Var tmpR: TRect; + IsSoft, IsFlat, IsMono: Boolean; +begin + + if grfFlags and BF_SOFT<>0 then + IsSoft:= true else + IsSoft:= false; + + if grfFlags and BF_FLAT<>0 then + IsFlat:= true else + IsFlat:= false; + + if grfFlags and BF_MONO<>0 then + IsMono:= true else + IsMono:= false; + + if grfFlags and BF_MIDDLE<>0 then + begin + Canvas.Fill.Color:= clBtnFace; + Canvas.FillRect(R, 0, 0, [], 1.0); + end; + tmpR:= R; + if grfFlags and BF_LEFT<>0 then + begin + tmpR:= R; + + if edge and BDR_RAISEDOUTER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= TColors.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= $FF646464; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end; + InflateRect(tmpR, -1, -1) + end; + + if edge and BDR_SUNKENOUTER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FF696969; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= $FF646464; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end; + InflateRect(tmpR, -1, -1) + end; + + if edge and BDR_RAISEDINNER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFF0F0F0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end; + end; + + if edge and BDR_SUNKENINNER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFF0F0F0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= $FF696969; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Left, tmpR.Bottom), 1.0); + end; + end; + end; + + if grfFlags and BF_TOP<>0 then + begin + tmpR:= R; + + if edge and BDR_RAISEDOUTER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= $FF646464; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end; + InflateRect(tmpR, -1, -1) + end; + + if edge and BDR_SUNKENOUTER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FF696969; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= $FF646464; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end; + InflateRect(tmpR, -1, -1) + end; + + if edge and BDR_RAISEDINNER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFF0F0F0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end; + end; + + if edge and BDR_SUNKENINNER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFF0F0F0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end else + begin + Canvas.Stroke.Color:= $FF696969; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Top), Point(tmpR.Right, tmpR.Top), 1.0); + end; + end; + + + end; + + if grfFlags and BF_RIGHT<>0 then + begin + tmpR:= R; + if edge and BDR_RAISEDOUTER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FF696969; + Canvas.DrawLine(Point(tmpR.Right-1, tmpR.Top), Point(tmpR.Right-1, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Right-1, tmpR.Top), Point(tmpR.Right-1, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= $FF646464; + Canvas.DrawLine(Point(tmpR.Right-1, tmpR.Top), Point(tmpR.Right-1, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= $FF696969; + Canvas.DrawLine(Point(tmpR.Right-1, tmpR.Top), Point(tmpR.Right-1, tmpR.Bottom), 1.0); + end; + InflateRect(tmpR, -1, -1) + end; + + if edge and BDR_SUNKENOUTER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Right-1, tmpR.Top), Point(tmpR.Right-1, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Right-1, tmpR.Top), Point(tmpR.Right-1, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= $FF646464; + Canvas.DrawLine(Point(tmpR.Right-1, tmpR.Top), Point(tmpR.Right-1, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Right-1, tmpR.Top), Point(tmpR.Right-1, tmpR.Bottom), 1.0); + end; + InflateRect(tmpR, -1, -1) + end; + + Dec(tmpR.Right); + + if edge and BDR_RAISEDINNER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Right, tmpR.Top), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFF0F0F0; + Canvas.DrawLine(Point(tmpR.Right, tmpR.Top), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Right, tmpR.Top), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Right, tmpR.Top), Point(tmpR.Right, tmpR.Bottom), 1.0); + end; + end; + + if edge and BDR_SUNKENINNER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Right, tmpR.Top), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Right, tmpR.Top), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Right, tmpR.Top), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Right, tmpR.Top), Point(tmpR.Right, tmpR.Bottom), 1.0); + end; + end; + end; + + if grfFlags and BF_BOTTOM<>0 then + begin + tmpR:= R; + Dec(tmpR.Bottom); + if edge and BDR_RAISEDOUTER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FF696969; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= $FF646464; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= $FF696969; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end; + InflateRect(tmpR, -1, -1) + end; + + if edge and BDR_SUNKENOUTER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= $FF646464; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end; + InflateRect(tmpR, -1, -1) + end; + + if edge and BDR_RAISEDINNER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFF0F0F0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= TAlphaColorRec.White; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= $FFA0A0A0; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end; + end; + + if edge and BDR_SUNKENINNER<>0 then + begin + if isSoft then + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if IsFlat then + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + if isMono then + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end else + begin + Canvas.Stroke.Color:= $FFE3E3E3; + Canvas.DrawLine(Point(tmpR.Left, tmpR.Bottom), Point(tmpR.Right, tmpR.Bottom), 1.0); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure GetTextExtentPoint32W(ACanvas: TCanvas; CaptionText: String; Len: Integer; Var Size: TSize); +begin + Size.cx:= ACanvas.TextWidth(Copy(CaptionText, 1, Len)); + Size.cy:= ACanvas.TextHeight(Copy(CaptionText, 1, Len)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure GetTextMetrics(ACanvas: TCanvas; var TM: TTextMetric); +Var P: TPathData; + tx: TTextLayout; + R: TRectF; +begin +{ + tmHeight: Single; //The height (ascent + descent) of characters. + tmAscent: Single; //The ascent (units above the base line) of characters. + tmDescent: Single; //The descent (units below the base line) of characters. + tmInternalLeading: Single; //The amount of leading (space) inside the bounds set by the tmHeight member. Accent marks and other diacritical characters may occur in this area. The designer may set this member to zero + tmExternalLeading: Single; //The amount of extra leading (space) that the application adds between rows. Since this area is outside the font, it contains no marks and is not altered by text output calls in either OPAQUE or TRANSPARENT mode. The designer may set this member to zero. + tmAveCharWidth: Single; //The average width of characters in the font (generally defined as the width of the letter x ). This value does not include the overhang required for bold or italic characters. + tmMaxCharWidth: Single; //The width of the widest character in the font. + tmWeight: Single; //The weight of the font. + tmOverhang: Single; + tmDigitizedAspectX: Single; //The horizontal aspect of the device for which the font was designed. + tmDigitizedAspectY: Single; //The vertical aspect of the device for which the font was designed. The ratio of the tmDigitizedAspectX and tmDigitizedAspectY members is the aspect ratio of the device for which the font was designed. + tmFirstChar: WideChar; //The value of the first character defined in the font. + tmLastChar: WideChar; //The value of the last character defined in the font. + tmDefaultChar: WideChar; //The value of the character to be substituted for characters not in the font. + tmBreakChar: WideChar; //The value of the character that will be used to define word breaks for text justification. + tmItalic: Byte; //Specifies an italic font if it is nonzero. + tmUnderlined: Byte; //Specifies an underlined font if it is nonzero. + tmStruckOut: Byte; //A strikeout font if it is nonzero. + tmPitchAndFamily: Byte; //Specifies information about the pitch, the technology, and the family of a physical font. TMPF_FIXED_PITCH, TMPF_VECTOR, TMPF_TRUETYPE, TMPF_DEVICE + tmCharSet: Byte; //The character set of the font. The character set can be one of the following values. ANSI_CHARSET, GREEK_CHARSET.... +} + TM.tmExternalLeading:= 0; + TM.tmWeight:= 0; //boldness??? + TM.tmOverhang:= 0; + TM.tmDigitizedAspectX:= 0; + TM.tmDigitizedAspectY:= 0; + TM.tmFirstChar:= 'a'; //??? + TM.tmLastChar:= 'z'; //??? + TM.tmDefaultChar:= ' '; + TM.tmBreakChar:= ' '; + TM.tmItalic:= 0; + TM.tmUnderlined:= 0; + TM.tmStruckOut:= 0; + TM.tmPitchAndFamily:= 0; + TM.tmCharSet:= 0; + + tx:= TTextLayoutManager.DefaultTextLayout.Create(ACanvas); + P:= TPathData.Create; + try + tx.Text:= 'W'; + tx.ConvertToPath(p); + R:= P.GetBounds(); + + TM.tmHeight:= R.Height; + TM.tmMaxCharWidth:= R.Width; + + //------------------------------------ + tx.Text:= 'Ó'; + p.Clear; + tx.ConvertToPath(p); + R:= P.GetBounds(); + TM.tmInternalLeading:= R.Height - TM.tmHeight; + + //------------------------------------ + tx.Text:= 'x'; + p.Clear; + tx.ConvertToPath(p); + R:= P.GetBounds(); + TM.tmAscent:= R.Height - TM.tmHeight; + TM.tmAveCharWidth:= R.Width; + + //------------------------------------ + tx.Text:= 'y'; + p.Clear; + tx.ConvertToPath(p); + TM.tmDescent:= P.GetBounds().Height - R.Height; + TM.tmHeight:= TM.tmHeight + TM.tmDescent; + finally + FreeAndNil(P); + FreeAndNil(tx); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function Rect(ALeft, ATop, ARight, ABottom: Single): TRect; +begin + Result:= RectF(ALeft, ATop, ARight, ABottom); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function Rect(const ATopLeft, ABottomRight: TPoint): TRect; +begin + Result:= RectF(ATopLeft.X, ATopLeft.Y, ABottomRight.X, ABottomRight.Y); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function Point(AX, AY: Single): TPoint; +begin + Result.X:= AX; + Result.Y:= AY; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure Inc(Var V: Single; OIle: Single=1.0); +begin + V:= V + OIle; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure Dec(Var V: Single; OIle: Single=1.0); +begin + V:= V - OIle; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function MulDiv(const A, B, C: Single): Single; +begin + Result:= (A * B) / C; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure FillMemory(Destination: Pointer; Length: NativeUInt; Fill: Byte); +begin + FillChar(Destination^, Length, Fill); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure ZeroMemory(Destination: Pointer; Length: NativeUInt); +begin + FillChar(Destination^, Length, 0); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure MoveMemory(Destination: Pointer; Source: Pointer; Length: NativeUInt); +begin + Move(Source^, Destination^, Length); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure CopyMemory(Destination: Pointer; Source: Pointer; Length: NativeUInt); +begin + Move(Source^, Destination^, Length); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure ChangeBiDiModeAlignment(var Alignment: TAlignment); +begin + case Alignment of + taLeftJustify: Alignment := taRightJustify; + taRightJustify: Alignment := taLeftJustify; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure OleUninitialize(); +begin + //nothing +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function timeGetTime: Int64; +begin + Result:= TThread.GetTickCount; +end; + +{ TChangeLink } + +//---------------------------------------------------------------------------------------------------------------------- + +constructor TChangeLink.Create; +begin + inherited; + IgnoreIndex := True; + IgnoreImages := True; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TChangeLink.GetSender: TCustomImageList; +begin + Result := TCustomImageList(Images); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TChangeLink.SetSender(const Value: TCustomImageList); +begin + Images := TBaseImageList(Value); +end; + +{ THighQualityBitmap } + +constructor THighQualityBitmap.Create; +begin + + inherited; + +end; + +{ TFontHelper } + +function TFontHelper.GetOnChange: TNotifyEvent; +begin + Result:= OnChanged; +end; + +procedure TFontHelper.SetOnChange(const Value: TNotifyEvent); +begin + OnChanged:= Value; +end; + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.Header.pas b/components/virtualtreeview/Source/VirtualTrees.Header.pas new file mode 100644 index 000000000..c0e556443 --- /dev/null +++ b/components/virtualtreeview/Source/VirtualTrees.Header.pas @@ -0,0 +1,5928 @@ +unit VirtualTrees.Header; + +interface + +uses + WinApi.Windows, + WinApi.Messages, + System.Classes, + System.Types, + System.Generics.Collections, + Vcl.Graphics, + Vcl.Menus, + Vcl.ImgList, + Vcl.Controls, + Vcl.Themes, + Vcl.GraphUtil, + System.UITypes, // some types moved from Vcl.* to System.UITypes + VirtualTrees.StyleHooks, + VirtualTrees.Utils, + VirtualTrees.Types, + VirtualTrees.DragImage; + + +{$MINENUMSIZE 1, make enumerations as small as possible} + + +const + DefaultColumnOptions = [coAllowClick, coDraggable, coEnabled, coParentColor, coParentBidiMode, coResizable, + coShowDropMark, coVisible, coAllowFocus, coEditable, coStyleColor]; + +type + TVTHeader = class; + TVirtualTreeColumn = class; + + // This structure carries all important information about header painting and is used in the advanced header painting. + THeaderPaintInfo = record + TargetCanvas : TCanvas; + Column : TVirtualTreeColumn; + PaintRectangle : TRect; + TextRectangle : TRect; + IsHoverIndex, + IsDownIndex, + IsEnabled, + ShowHeaderGlyph, + ShowSortGlyph, + ShowRightBorder : Boolean; + DropMark : TVTDropMarkMode; + GlyphPos, + SortGlyphPos : TPoint; + SortGlyphSize : TSize; + procedure DrawSortArrow(pDirection : TSortDirection); + procedure DrawDropMark(); + end; + + TVirtualTreeColumns = class; + + TVirtualTreeColumn = class(TCollectionItem) + private + const + cDefaultColumnSpacing = 3; + private + FText, + FHint : string; + FWidth : TDimension; + FPosition : TColumnPosition; + FMinWidth : TDimension; + FMaxWidth : TDimension; + FStyle : TVirtualTreeColumnStyle; + FImageIndex : TImageIndex; + FBiDiMode : TBiDiMode; + FLayout : TVTHeaderColumnLayout; + FMargin, + FSpacing : TDimension; + FOptions : TVTColumnOptions; + FEditOptions : TVTEditOptions; + FEditNextColumn : TDimension; + FTag : NativeInt; + FAlignment : TAlignment; + FCaptionAlignment : TAlignment; // Alignment of the caption. + FLastWidth : TDimension; + FColor : TColor; + FBonusPixel : Boolean; + FSpringRest : Single; // Accumulator for width adjustment when auto spring option is enabled. + FCaptionText : string; + FCheckBox : Boolean; + FCheckType : TCheckType; + FCheckState : TCheckState; + FImageRect : TRect; + FHasImage : Boolean; + FDefaultSortDirection : TSortDirection; + function GetCaptionAlignment : TAlignment; + function GetCaptionWidth : TDimension; + function GetLeft : TDimension; + function IsBiDiModeStored : Boolean; + function IsCaptionAlignmentStored : Boolean; + function IsColorStored : Boolean; + procedure SetAlignment(const Value : TAlignment); + procedure SetBiDiMode(Value : TBiDiMode); + procedure SetCaptionAlignment(const Value : TAlignment); + procedure SetCheckBox(Value : Boolean); + procedure SetCheckState(Value : TCheckState); + procedure SetCheckType(Value : TCheckType); + procedure SetColor(const Value : TColor); + procedure SetImageIndex(Value : TImageIndex); + procedure SetLayout(Value : TVTHeaderColumnLayout); + procedure SetMargin(Value : TDimension); + procedure SetMaxWidth(Value : TDimension); + procedure SetMinWidth(Value : TDimension); + procedure SetOptions(Value : TVTColumnOptions); + procedure SetPosition(Value : TColumnPosition); + procedure SetSpacing(Value : TDimension); + procedure SetStyle(Value : TVirtualTreeColumnStyle); + + protected + FLeft : TDimension; + procedure ChangeScale(M, D : TDimension); virtual; + procedure ComputeHeaderLayout(var PaintInfo : THeaderPaintInfo; DrawFormat : Cardinal; CalculateTextRect : Boolean = False); + procedure DefineProperties(Filer : TFiler); override; + procedure GetAbsoluteBounds(var Left, Right : TDimension); + function GetDisplayName : string; override; + function GetText : string; virtual; // [IPK] + procedure SetText(const Value : string); virtual; // [IPK] private to protected & virtual + function GetOwner : TVirtualTreeColumns; reintroduce; + procedure InternalSetWidth(const Value : TDimension); //bypass side effects in SetWidth + procedure ReadHint(Reader : TReader); + procedure ReadText(Reader : TReader); + procedure SetCollection(Value : TCollection); override; + procedure SetWidth(Value : TDimension); + public + constructor Create(Collection : TCollection); override; + destructor Destroy; override; + + procedure Assign(Source : TPersistent); override; + function Equals(OtherColumnObj : TObject) : Boolean; override; + function GetRect : TRect; virtual; + property HasImage : Boolean read FHasImage; + property ImageRect : TRect read FImageRect; + procedure LoadFromStream(const Stream : TStream; Version : Integer); + procedure ParentBiDiModeChanged; + procedure ParentColorChanged; + procedure RestoreLastWidth; + function GetEffectiveColor() : TColor; + procedure SaveToStream(const Stream : TStream); + function UseRightToLeftReading : Boolean; + + property BonusPixel : Boolean read FBonusPixel write FBonusPixel; + property CaptionText : string read FCaptionText; + property LastWidth : TDimension read FLastWidth; + property Left : TDimension read GetLeft; + property Owner : TVirtualTreeColumns read GetOwner; + property SpringRest : Single read FSpringRest write FSpringRest; + published + property Alignment : TAlignment read FAlignment write SetAlignment default taLeftJustify; + property BiDiMode : TBiDiMode read FBiDiMode write SetBiDiMode stored IsBiDiModeStored; + property CaptionAlignment : TAlignment read GetCaptionAlignment write SetCaptionAlignment + stored IsCaptionAlignmentStored default taLeftJustify; + property CaptionWidth : TDimension read GetCaptionWidth; + property CheckType : TCheckType read FCheckType write SetCheckType default ctCheckBox; + property CheckState : TCheckState read FCheckState write SetCheckState default csUncheckedNormal; + property CheckBox : Boolean read FCheckBox write SetCheckBox default False; + property Color : TColor read FColor write SetColor stored IsColorStored; + property DefaultSortDirection : TSortDirection read FDefaultSortDirection write FDefaultSortDirection default sdAscending; + property Hint : string read FHint write FHint; + property ImageIndex : TImageIndex read FImageIndex write SetImageIndex default - 1; + property Layout : TVTHeaderColumnLayout read FLayout write SetLayout default blGlyphLeft; + property Margin : TDimension read FMargin write SetMargin default 4; + property MaxWidth : TDimension read FMaxWidth write SetMaxWidth default 10000; + property MinWidth : TDimension read FMinWidth write SetMinWidth default 10; + property Options : TVTColumnOptions read FOptions write SetOptions default DefaultColumnOptions; + property EditOptions : TVTEditOptions read FEditOptions write FEditOptions default toDefaultEdit; + property EditNextColumn : TDimension read FEditNextColumn write FEditNextColumn default - 1; + property Position : TColumnPosition read FPosition write SetPosition; + property Spacing : TDimension read FSpacing write SetSpacing default cDefaultColumnSpacing; + property Style : TVirtualTreeColumnStyle read FStyle write SetStyle default vsText; + property Tag : NativeInt read FTag write FTag default 0; + property Text : string read GetText write SetText; + property Width : TDimension read FWidth write SetWidth default 50; + end; + + TVirtualTreeColumnClass = class of TVirtualTreeColumn; + + TColumnsArray = array of TVirtualTreeColumn; + TCardinalArray = array of Cardinal; + TIndexArray = array of TColumnIndex; + + TVirtualTreeColumns = class(TCollection) + private + FHeader : TVTHeader; + FHeaderBitmap : TBitmap; // backbuffer for drawing + FHoverIndex, // currently "hot" column + FDownIndex, // Column on which a mouse button is held down. + FTrackIndex : TColumnIndex; // Index of column which is currently being resized. + FClickIndex : TColumnIndex; // Index of the last clicked column. + FCheckBoxHit : Boolean; // True if the last click was on a header checkbox. + FPositionToIndex : TIndexArray; + FDefaultWidth : TDimension; // the width columns are created with + FNeedPositionsFix : Boolean; // True if FixPositions must still be called after DFM loading or Bidi mode change. + FClearing : Boolean; // True if columns are being deleted entirely. + FColumnPopupMenu : TPopupMenu; // Member for storing the TVTHeaderPopupMenu + function GetCount : Integer; + function GetItem(Index : TColumnIndex) : TVirtualTreeColumn; + function GetNewIndex(P : TPoint; var OldIndex : TColumnIndex) : Boolean; + procedure SetDefaultWidth(Value : TDimension); + procedure SetItem(Index : TColumnIndex; Value : TVirtualTreeColumn); + function GetTreeView: TCustomControl; + protected + // drag support + FDragIndex : TColumnIndex; // index of column currently being dragged + FDropTarget : TColumnIndex; // current target column (index) while dragging + FDropBefore : Boolean; // True if drop position is in the left half of a column, False for the right + // side to drop the dragged column to + + procedure AdjustAutoSize(CurrentIndex : TColumnIndex; Force : Boolean = False); + function AdjustDownColumn(P : TPoint) : TColumnIndex; + function AdjustHoverColumn(P : TPoint) : Boolean; + procedure AdjustPosition(Column : TVirtualTreeColumn; Position : Cardinal); + function CanSplitterResize(P : TPoint; Column : TColumnIndex) : Boolean; + procedure DoCanSplitterResize(P : TPoint; Column : TColumnIndex; var Allowed : Boolean); virtual; + procedure DrawButtonText(DC : HDC; Caption : string; Bounds : TRect; Enabled, Hot : Boolean; DrawFormat : Cardinal; + WrapCaption : Boolean); + procedure FixPositions; + function GetColumnAndBounds(P : TPoint; var ColumnLeft, ColumnRight : TDimension; Relative : Boolean = True) : Integer; + function GetOwner : TPersistent; override; + function HandleClick(P : TPoint; Button : TMouseButton; Force, DblClick : Boolean) : Boolean; virtual; + procedure HeaderPopupMenuAddHeaderPopupItem(const Sender : TObject; const Column : TColumnIndex; var Cmd : TAddPopupItemType); + procedure IndexChanged(OldIndex, NewIndex : Integer); + procedure InitializePositionArray; + procedure Notify(Item : TCollectionItem; Action : System.Classes.TCollectionNotification); override; + procedure ReorderColumns(RTL : Boolean); + procedure SetHoverIndex(Index : TColumnIndex); + procedure Update(Item : TCollectionItem); override; + procedure UpdatePositions(Force : Boolean = False); + + property HeaderBitmap : TBitmap read FHeaderBitmap; + property PositionToIndex : TIndexArray read FPositionToIndex; + property HoverIndex : TColumnIndex read FHoverIndex write FHoverIndex; + property DownIndex : TColumnIndex read FDownIndex write FDownIndex; + property CheckBoxHit : Boolean read FCheckBoxHit write FCheckBoxHit; + // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) + function StyleServices(AControl : TControl = nil) : TCustomStyleServices; + public + constructor Create(AOwner : TVTHeader); virtual; + destructor Destroy; override; + + function Add : TVirtualTreeColumn; virtual; + procedure AnimatedResize(Column : TColumnIndex; NewWidth : TDimension); + procedure Assign(Source : TPersistent); override; + procedure Clear; virtual; + function ColumnFromPosition(P : TPoint; Relative : Boolean = True) : TColumnIndex; overload; virtual; + function ColumnFromPosition(PositionIndex : TColumnPosition) : TColumnIndex; overload; virtual; + function Equals(OtherColumnsObj : TObject) : Boolean; override; + procedure GetColumnBounds(Column : TColumnIndex; var Left, Right : TDimension); + function GetFirstVisibleColumn(ConsiderAllowFocus : Boolean = False) : TColumnIndex; + function GetLastVisibleColumn(ConsiderAllowFocus : Boolean = False) : TColumnIndex; + function GetFirstColumn : TColumnIndex; + function GetNextColumn(Column : TColumnIndex) : TColumnIndex; + function GetNextVisibleColumn(Column : TColumnIndex; ConsiderAllowFocus : Boolean = False) : TColumnIndex; + function GetPreviousColumn(Column : TColumnIndex) : TColumnIndex; + function GetPreviousVisibleColumn(Column : TColumnIndex; ConsiderAllowFocus : Boolean = False) : TColumnIndex; + function GetScrollWidth : TDimension; + function GetVisibleColumns : TColumnsArray; + function GetVisibleFixedWidth : TDimension; + function IsValidColumn(Column : TColumnIndex) : Boolean; + procedure LoadFromStream(const Stream : TStream; Version : Integer); + procedure PaintHeader(DC : HDC; R : TRect; HOffset : TDimension); overload; virtual; + procedure PaintHeader(TargetCanvas : TCanvas; R : TRect; const Target : TPoint; + RTLOffset : TDimension = 0); overload; virtual; + procedure SaveToStream(const Stream : TStream); + procedure EndUpdate(); override; + function TotalWidth : TDimension; + + property Count : Integer read GetCount; + property ClickIndex : TColumnIndex read FClickIndex write FClickIndex; + property DefaultWidth : TDimension read FDefaultWidth write SetDefaultWidth; + property DragIndex : TColumnIndex read FDragIndex write FDragIndex; + property DropBefore : Boolean read FDropBefore write FDropBefore; + property DropTarget : TColumnIndex read FDropTarget write FDropTarget; + property Items[Index : TColumnIndex] : TVirtualTreeColumn read GetItem write SetItem; default; + property Header: TVTHeader read FHeader; + property TrackIndex : TColumnIndex read FTrackIndex write FTrackIndex; + property TreeView : TCustomControl read GetTreeView; + property UpdateCount; + end; + + TVirtualTreeColumnsClass = class of TVirtualTreeColumns; + + TVTConstraintPercent = 0 .. 100; + + TVTFixedAreaConstraints = class(TPersistent) + private + FHeader : TVTHeader; + FMaxHeightPercent, FMaxWidthPercent, FMinHeightPercent, FMinWidthPercent : TVTConstraintPercent; + FOnChange : TNotifyEvent; + procedure SetConstraints(Index : Integer; Value : TVTConstraintPercent); + protected + procedure Change; + property Header : TVTHeader read FHeader; + public + constructor Create(AOwner : TVTHeader); + + procedure Assign(Source : TPersistent); override; + property OnChange : TNotifyEvent read FOnChange write FOnChange; + published + property MaxHeightPercent : TVTConstraintPercent index 0 read FMaxHeightPercent write SetConstraints default 0; + property MaxWidthPercent : TVTConstraintPercent index 1 read FMaxWidthPercent write SetConstraints default 95; + property MinHeightPercent : TVTConstraintPercent index 2 read FMinHeightPercent write SetConstraints default 0; + property MinWidthPercent : TVTConstraintPercent index 3 read FMinWidthPercent write SetConstraints default 0; + end; + + TVTHeader = class(TPersistent) + private + FOwner : TCustomControl; + FColumns : TVirtualTreeColumns; + FHeight : TDimension; + FFont : TFont; + FParentFont : Boolean; + FOptions : TVTHeaderOptions; + FStyle : TVTHeaderStyle; //button style + FBackgroundColor : TColor; + FAutoSizeIndex : TColumnIndex; + FPopupMenu : TPopupMenu; + FMainColumn : TColumnIndex; //the column which holds the tree + FMaxHeight : TDimension; + FMinHeight : TDimension; + FDefaultHeight : TDimension; + FFixedAreaConstraints : TVTFixedAreaConstraints; //Percentages for the fixed area (header, fixed columns). + FImages : TCustomImageList; + FImageChangeLink : TChangeLink; //connections to the image list to get notified about changes + fSplitterHitTolerance : TDimension; //For property SplitterHitTolerance + FSortColumn : TColumnIndex; + FSortDirection : TSortDirection; + FDragImage : TVTDragImage; //drag image management during header drag + FLastWidth : TDimension; //Used to adjust spring columns. This is the width of all visible columns, not the header rectangle. + FRestoreSelectionColumnIndex : Integer; //The column that is used to implement the coRestoreSelection option + FWasDoubleClick : Boolean; // The previous mouse message was for a double click, that allows us to process mouse-up-messages differently + function GetMainColumn : TColumnIndex; + function GetUseColumns : Boolean; + function IsFontStored : Boolean; + procedure SetAutoSizeIndex(Value : TColumnIndex); + procedure SetBackground(Value : TColor); + procedure SetColumns(Value : TVirtualTreeColumns); + procedure SetDefaultHeight(Value : TDimension); + procedure SetFont(const Value : TFont); + procedure SetHeight(Value : TDimension); + procedure SetImages(const Value : TCustomImageList); + procedure SetMainColumn(Value : TColumnIndex); + procedure SetMaxHeight(Value : TDimension); + procedure SetMinHeight(Value : TDimension); + procedure SetOptions(Value : TVTHeaderOptions); + procedure SetParentFont(Value : Boolean); + procedure SetSortColumn(Value : TColumnIndex); + procedure SetSortDirection(const Value : TSortDirection); + procedure SetStyle(Value : TVTHeaderStyle); + function GetRestoreSelectionColumnIndex : Integer; + function AreColumnsStored: Boolean; + protected + FStates : THeaderStates; //Used to keep track of internal states the header can enter. + FDragStart : TPoint; //initial mouse drag position + FTrackStart : TPoint; //client coordinates of the tracking start point + FTrackPoint : TPoint; //Client coordinate where the tracking started. + FDoingAutoFitColumns : Boolean; //Flag to avoid using the stored width for Main column + + procedure FontChanged(Sender : TObject); virtual; + procedure AutoScale(); virtual; + function CanSplitterResize(P : TPoint) : Boolean; + function CanWriteColumns : Boolean; virtual; + procedure ChangeScale(M, D : TDimension); virtual; + function DetermineSplitterIndex(P : TPoint) : Boolean; virtual; + procedure DoAfterAutoFitColumn(Column : TColumnIndex); virtual; + procedure DoAfterColumnWidthTracking(Column : TColumnIndex); virtual; + procedure DoAfterHeightTracking; virtual; + function DoBeforeAutoFitColumn(Column : TColumnIndex; SmartAutoFitType : TSmartAutoFitType) : Boolean; virtual; + procedure DoBeforeColumnWidthTracking(Column : TColumnIndex; Shift : TShiftState); virtual; + procedure DoBeforeHeightTracking(Shift : TShiftState); virtual; + procedure DoCanSplitterResize(P : TPoint; var Allowed : Boolean); virtual; + function DoColumnWidthDblClickResize(Column : TColumnIndex; P : TPoint; Shift : TShiftState) : Boolean; virtual; + function DoColumnWidthTracking(Column : TColumnIndex; Shift : TShiftState; var TrackPoint : TPoint; P : TPoint) : Boolean; virtual; + function DoGetPopupMenu(Column : TColumnIndex; Position : TPoint) : TPopupMenu; virtual; + function DoHeightTracking(var P : TPoint; Shift : TShiftState) : Boolean; virtual; + function DoHeightDblClickResize(var P : TPoint; Shift : TShiftState) : Boolean; virtual; + procedure DoSetSortColumn(Value : TColumnIndex; pSortDirection : TSortDirection); virtual; + procedure FixedAreaConstraintsChanged(Sender : TObject); + function GetColumnsClass : TVirtualTreeColumnsClass; virtual; + function GetOwner : TPersistent; override; + function GetShiftState : TShiftState; + function HandleHeaderMouseMove(var Message : TWMMouseMove) : Boolean; + function HandleMessage(var Message : TMessage) : Boolean; virtual; + procedure ImageListChange(Sender : TObject); + procedure PrepareDrag(P, Start : TPoint); + procedure ReadColumns(Reader : TReader); + procedure RecalculateHeader; virtual; + procedure RescaleHeader; + procedure UpdateMainColumn; + procedure UpdateSpringColumns; + procedure WriteColumns(Writer : TWriter); + procedure InternalSetMainColumn(const Index : TColumnIndex); + procedure InternalSetAutoSizeIndex(const Index : TColumnIndex); + procedure InternalSetSortColumn(const Index : TColumnIndex); + public + constructor Create(AOwner : TCustomControl); virtual; + destructor Destroy; override; + + function AllowFocus(ColumnIndex : TColumnIndex) : Boolean; + procedure Assign(Source : TPersistent); override; + procedure AutoFitColumns(); overload; + procedure AutoFitColumns(Animated : Boolean; SmartAutoFitType : TSmartAutoFitType = smaUseColumnOption; RangeStartCol : Integer = NoColumn; RangeEndCol : Integer = NoColumn); overload; virtual; + procedure ColumnDropped(const P: TPoint); + procedure DragTo(P : TPoint); + function InHeader(P : TPoint) : Boolean; virtual; + function InHeaderSplitterArea(P : TPoint) : Boolean; virtual; + procedure Invalidate(Column : TVirtualTreeColumn; ExpandToBorder : Boolean = False; UpdateNowFlag : Boolean = False); + procedure LoadFromStream(const Stream : TStream); virtual; + function ResizeColumns(ChangeBy : TDimension; RangeStartCol : TColumnIndex; RangeEndCol : TColumnIndex; Options : TVTColumnOptions = [coVisible]) : TDimension; + procedure RestoreColumns; + procedure SaveToStream(const Stream : TStream); virtual; + procedure StyleChanged(); virtual; + procedure ToggleSortDirection(); + + property DragImage : TVTDragImage read FDragImage; + property RestoreSelectionColumnIndex : Integer read GetRestoreSelectionColumnIndex write FRestoreSelectionColumnIndex default NoColumn; + property States : THeaderStates read FStates; + property Treeview : TCustomControl read FOwner; + property UseColumns : Boolean read GetUseColumns; + property doingAutoFitColumns : Boolean read FDoingAutoFitColumns; + published + property AutoSizeIndex : TColumnIndex read FAutoSizeIndex write SetAutoSizeIndex; + property Background : TColor read FBackgroundColor write SetBackground default clBtnFace; + property Columns : TVirtualTreeColumns read FColumns write SetColumns stored AreColumnsStored; + property DefaultHeight : TDimension read FDefaultHeight write SetDefaultHeight default 19; + property Font : TFont read FFont write SetFont stored IsFontStored; + property FixedAreaConstraints : TVTFixedAreaConstraints read FFixedAreaConstraints write FFixedAreaConstraints; + property Height : TDimension read FHeight write SetHeight default 19; + property Images : TCustomImageList read FImages write SetImages; + property MainColumn : TColumnIndex read GetMainColumn write SetMainColumn default 0; + property MaxHeight : TDimension read FMaxHeight write SetMaxHeight default 10000; + property MinHeight : TDimension read FMinHeight write SetMinHeight default 10; + property Options : TVTHeaderOptions read FOptions write SetOptions default [hoColumnResize, hoDrag, hoShowSortGlyphs]; + property ParentFont : Boolean read FParentFont write SetParentFont default True; + property PopupMenu : TPopupMenu read FPopupMenu write FPopupMenu; + property SortColumn : TColumnIndex read FSortColumn write SetSortColumn default NoColumn; + property SortDirection : TSortDirection read FSortDirection write SetSortDirection default sdAscending; + property SplitterHitTolerance : TDimension read fSplitterHitTolerance write fSplitterHitTolerance default 8; + //The area in pixels around a spliter which is sensitive for resizing + property Style : TVTHeaderStyle read FStyle write SetStyle default hsThickButtons; + end; + + TVTHeaderClass = class of TVTHeader; + +implementation + +uses + WinApi.ShlObj, + WinApi.ActiveX, + WinApi.UxTheme, + System.Math, + System.SysUtils, + System.Generics.Defaults, + Vcl.Forms, + VirtualTrees.HeaderPopup, + VirtualTrees.BaseTree, + VirtualTrees.BaseAncestorVcl, // to eliminate H2443 about inline expanding + VirtualTrees.DataObject; + +type + TVirtualTreeColumnsCracker = class(TVirtualTreeColumns); + TVirtualTreeColumnCracker = class(TVirtualTreeColumn); + TBaseVirtualTreeCracker = class(TBaseVirtualTree); + + TVTHeaderHelper = class helper for TVTHeader + public + function Tree : TBaseVirtualTreeCracker; + end; + + TVirtualTreeColumnHelper = class helper for TVirtualTreeColumn + function TreeViewControl : TBaseVirtualTreeCracker; + function Header : TVTHeader; + end; + + TVirtualTreeColumnsHelper = class helper for TVirtualTreeColumns + function TreeViewControl : TBaseVirtualTreeCracker; + end; + +const + cMargin = 2; // the margin between text and the header rectangle + cDownOffset = 1; // the offset of the column header text whit mouse button down + + + //----------------- TVTFixedAreaConstraints ---------------------------------------------------------------------------- + +constructor TVTFixedAreaConstraints.Create(AOwner : TVTHeader); + +begin + inherited Create; + FMaxWidthPercent := 95; + FHeader := AOwner; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTFixedAreaConstraints.SetConstraints(Index : Integer; Value : TVTConstraintPercent); + +begin + case Index of + 0 : + if Value <> FMaxHeightPercent then + begin + FMaxHeightPercent := Value; + if (Value > 0) and (Value < FMinHeightPercent) then + FMinHeightPercent := Value; + Change; + end; + 1 : + if Value <> FMaxWidthPercent then + begin + FMaxWidthPercent := Value; + if (Value > 0) and (Value < FMinWidthPercent) then + FMinWidthPercent := Value; + Change; + end; + 2 : + if Value <> FMinHeightPercent then + begin + FMinHeightPercent := Value; + if (FMaxHeightPercent > 0) and (Value > FMaxHeightPercent) then + FMaxHeightPercent := Value; + Change; + end; + 3 : + if Value <> FMinWidthPercent then + begin + FMinWidthPercent := Value; + if (FMaxWidthPercent > 0) and (Value > FMaxWidthPercent) then + FMaxWidthPercent := Value; + Change; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTFixedAreaConstraints.Change; + +begin + if Assigned(FOnChange) then + FOnChange(Self); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTFixedAreaConstraints.Assign(Source : TPersistent); + +begin + if Source is TVTFixedAreaConstraints then + begin + FMaxHeightPercent := TVTFixedAreaConstraints(Source).FMaxHeightPercent; + FMaxWidthPercent := TVTFixedAreaConstraints(Source).FMaxWidthPercent; + FMinHeightPercent := TVTFixedAreaConstraints(Source).FMinHeightPercent; + FMinWidthPercent := TVTFixedAreaConstraints(Source).FMinWidthPercent; + Change; + end + else + inherited; +end; + +//----------------- TVTHeader ----------------------------------------------------------------------------------------- + +constructor TVTHeader.Create(AOwner : TCustomControl); + +begin + inherited Create; + FOwner := AOwner; + FColumns := GetColumnsClass.Create(Self); + FHeight := 19; + FDefaultHeight := FHeight; + FMinHeight := 10; + FMaxHeight := 10000; + FFont := TFont.Create; + FFont.OnChange := FontChanged; + FParentFont := True; + FBackgroundColor := clBtnFace; + FOptions := [hoColumnResize, hoDrag, hoShowSortGlyphs]; + + FImageChangeLink := TChangeLink.Create; + FImageChangeLink.OnChange := ImageListChange; + + FSortColumn := NoColumn; + FSortDirection := sdAscending; + FMainColumn := NoColumn; + + FDragImage := TVTDragImage.Create(AOwner); + fSplitterHitTolerance := 8; + FFixedAreaConstraints := TVTFixedAreaConstraints.Create(Self); + FFixedAreaConstraints.OnChange := FixedAreaConstraintsChanged; + + FDoingAutoFitColumns := False; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +destructor TVTHeader.Destroy; + +begin + FDragImage.Free; + FFixedAreaConstraints.Free; + FImageChangeLink.Free; + FFont.Free; + FColumns.Clear; //TCollection's Clear method is not virtual, so we have to call our own Clear method manually. + FColumns.Free; + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.FontChanged(Sender : TObject); +begin + inherited; + AutoScale(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.AutoScale(); +var + I : Integer; + lMaxHeight : TDimension; +begin + if (toAutoChangeScale in TBaseVirtualTreeCracker(Tree).TreeOptions.AutoOptions) then + begin + //Ensure a minimum header size based on the font, so that all text is visible. + //First find the largest Columns[].Spacing + lMaxHeight := 0; + for I := 0 to Self.Columns.Count - 1 do + lMaxHeight := Max(lMaxHeight, Columns[I].Spacing); + //Calculate the required height based on the font, this is important as the user might just have increased the size of the system icon font. + with TBitmap.Create do + try + Canvas.Font.Assign(FFont); + lMaxHeight := lMaxHeight { top spacing } + Divide(lMaxHeight, 2) { minimum bottom spacing } + Canvas.TextHeight('Q'); + finally + Free; + end; + //Set the calculated size + Self.SetHeight(lMaxHeight); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.GetMainColumn : TColumnIndex; +begin + if FColumns.Count > 0 then + Result := FMainColumn + else + Result := NoColumn; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.GetUseColumns : Boolean; +begin + Result := FColumns.Count > 0; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.IsFontStored : Boolean; +begin + Result := not ParentFont; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetAutoSizeIndex(Value : TColumnIndex); +begin + if FAutoSizeIndex <> Value then + begin + FAutoSizeIndex := Value; + if hoAutoResize in FOptions then + TVirtualTreeColumnsCracker(Columns).AdjustAutoSize(InvalidColumn); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetBackground(Value : TColor); +begin + if FBackgroundColor <> Value then + begin + FBackgroundColor := Value; + Invalidate(nil); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetColumns(Value : TVirtualTreeColumns); + +begin + FColumns.Assign(Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetDefaultHeight(Value : TDimension); +begin + if Value < FMinHeight then + Value := FMinHeight; + if Value > FMaxHeight then + Value := FMaxHeight; + + if FHeight = FDefaultHeight then + SetHeight(Value); + FDefaultHeight := Value; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetFont(const Value : TFont); +begin + FFont.Assign(Value); + FParentFont := False; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetHeight(Value : TDimension); + +var + RelativeMaxHeight, RelativeMinHeight, EffectiveMaxHeight, EffectiveMinHeight : TDimension; +begin + if not Tree.HandleAllocated then + begin + FHeight := Value; + Include(FStates, hsNeedScaling); + end + else + begin + with FFixedAreaConstraints do + begin + RelativeMaxHeight := Divide((Tree.ClientHeight + FHeight) * FMaxHeightPercent, 100); + RelativeMinHeight := Divide((Tree.ClientHeight + FHeight) * FMinHeightPercent, 100); + + EffectiveMinHeight := IfThen(FMaxHeightPercent > 0, Min(RelativeMaxHeight, FMinHeight), FMinHeight); + EffectiveMaxHeight := IfThen(FMinHeightPercent > 0, Max(RelativeMinHeight, FMaxHeight), FMaxHeight); + + Value := Min(Max(Value, EffectiveMinHeight), EffectiveMaxHeight); + if FMinHeightPercent > 0 then + Value := Max(RelativeMinHeight, Value); + if FMaxHeightPercent > 0 then + Value := Min(RelativeMaxHeight, Value); + end; + + if FHeight <> Value then + begin + FHeight := Value; + if not (csLoading in Tree.ComponentState) and not (hsScaling in FStates) then + RecalculateHeader; + Tree.Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetImages(const Value : TCustomImageList); + +begin + if FImages <> Value then + begin + if Assigned(FImages) then + begin + FImages.UnRegisterChanges(FImageChangeLink); + FImages.RemoveFreeNotification(FOwner); + end; + FImages := Value; + if Assigned(FImages) then + begin + FImages.RegisterChanges(FImageChangeLink); + FImages.FreeNotification(FOwner); + end; + if not (csLoading in Tree.ComponentState) then + Invalidate(nil); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetMainColumn(Value : TColumnIndex); + +begin + if (csLoading in Tree.ComponentState) or (csDestroying in Tree.ComponentState) then + FMainColumn := Value + else + begin + if Value < 0 then + Value := 0; + if Value > FColumns.Count - 1 then + Value := FColumns.Count - 1; + if Value <> FMainColumn then + begin + FMainColumn := Value; + Tree.MainColumnChanged; + if not (toExtendedFocus in Tree.TreeOptions.SelectionOptions) then + Tree.FocusedColumn := FMainColumn; + Tree.Invalidate; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetMaxHeight(Value : TDimension); + +begin + if Value < FMinHeight then + Value := FMinHeight; + FMaxHeight := Value; + SetHeight(FHeight); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetMinHeight(Value : TDimension); + +begin + if Value < 0 then + Value := 0; + if Value > FMaxHeight then + Value := FMaxHeight; + FMinHeight := Value; + SetHeight(FHeight); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetOptions(Value : TVTHeaderOptions); + +var + ToBeSet, ToBeCleared : TVTHeaderOptions; + +begin + ToBeSet := Value - FOptions; + ToBeCleared := FOptions - Value; + FOptions := Value; + + if (hoAutoResize in (ToBeSet + ToBeCleared)) and (FColumns.Count > 0) then + begin + TVirtualTreeColumnsCracker(FColumns).AdjustAutoSize(InvalidColumn); + if Tree.HandleAllocated then + begin + Tree.UpdateHorizontalScrollBar(False); + if hoAutoResize in ToBeSet then + Tree.Invalidate; + end; + end; + + if not (csLoading in Tree.ComponentState) and Tree.HandleAllocated then + begin + if hoVisible in (ToBeSet + ToBeCleared) then + RecalculateHeader; + Invalidate(nil); + Tree.Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetParentFont(Value : Boolean); + +begin + if FParentFont <> Value then + begin + FParentFont := Value; + if FParentFont then + FFont.Assign(TBaseVirtualTree(FOwner).Font); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetSortColumn(Value : TColumnIndex); + +begin + if csLoading in Tree.ComponentState then + FSortColumn := Value + else + DoSetSortColumn(Value, FSortDirection); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetSortDirection(const Value : TSortDirection); + +begin + if Value <> FSortDirection then + begin + FSortDirection := Value; + Invalidate(nil); + if ((toAutoSort in Tree.TreeOptions.AutoOptions) or (hoHeaderClickAutoSort in Options)) and (Tree.UpdateCount = 0) then + Tree.SortTree(FSortColumn, FSortDirection, True); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.CanSplitterResize(P : TPoint) : Boolean; + +begin + Result := hoHeightResize in FOptions; + DoCanSplitterResize(P, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SetStyle(Value : TVTHeaderStyle); + +begin + if FStyle <> Value then + begin + FStyle := Value; + if not (csLoading in Tree.ComponentState) then + Invalidate(nil); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.StyleChanged(); +begin + AutoScale(); //Elements may have changed in size +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.CanWriteColumns : Boolean; + +//descendants may override this to optionally prevent column writing (e.g. if they are build dynamically). + +begin + Result := True; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.ChangeScale(M, D : TDimension); +var + I : Integer; +begin + //This method is only executed if toAutoChangeScale is set + FMinHeight := MulDiv(FMinHeight, M, D); + FMaxHeight := MulDiv(FMaxHeight, M, D); + Self.Height := MulDiv(FHeight, M, D); + //Scale the columns widths too + for I := 0 to FColumns.Count - 1 do + TVirtualTreeColumnCracker(Self.FColumns[I]).ChangeScale(M, D); + if not ParentFont then + Font.Height := MulDiv(Font.Height, M, D); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.DetermineSplitterIndex(P : TPoint) : Boolean; + +//Tries to find the index of that column whose right border corresponds to P. +//Result is True if column border was hit (with -3..+5 pixels tolerance). +//For continuous resizing the current track index and the column's left/right border are set. +//Note: The hit test is checking from right to left (or left to right in RTL mode) to make enlarging of zero-sized +//columns possible. + +var + VisibleFixedWidth : TDimension; + SplitPoint : TDimension; + + //--------------- local function -------------------------------------------- + + function IsNearBy(IsFixedCol : Boolean; LeftTolerance, RightTolerance : TDimension) : Boolean; + + begin + if IsFixedCol then + Result := (P.X < SplitPoint + Tree.EffectiveOffsetX + RightTolerance) and (P.X > SplitPoint + Tree.EffectiveOffsetX - LeftTolerance) + else + Result := (P.X > VisibleFixedWidth) and (P.X < SplitPoint + RightTolerance) and (P.X > SplitPoint - LeftTolerance); + end; + +//--------------- end local function ---------------------------------------- + +var + I : Integer; + LeftTolerance : TDimension; //The area left of the column divider which allows column resizing +begin + Result := False; + + if FColumns.Count > 0 then + begin + FColumns.TrackIndex := NoColumn; + VisibleFixedWidth := FColumns.GetVisibleFixedWidth; + LeftTolerance := Round(SplitterHitTolerance * 0.6); + if Tree.UseRightToLeftAlignment then + begin + SplitPoint := - Tree.EffectiveOffsetX; + if FColumns.TotalWidth < Tree.ClientWidth then + Inc(SplitPoint, Tree.ClientWidth - FColumns.TotalWidth); + + for I := 0 to FColumns.Count - 1 do + with TVirtualTreeColumnsCracker(FColumns), Items[PositionToIndex[I]] do + if coVisible in Options then + begin + if IsNearBy(coFixed in Options, LeftTolerance, SplitterHitTolerance - LeftTolerance) then + begin + if CanSplitterResize(P, PositionToIndex[I]) then + begin + Result := True; + TrackIndex := PositionToIndex[I]; + + //Keep the right border of this column. This and the current mouse position + //directly determine the current column width. + FTrackPoint.X := SplitPoint + IfThen(coFixed in Options, Tree.EffectiveOffsetX) + Width; + FTrackPoint.Y := P.Y; + Break; + end; + end; + Inc(SplitPoint, Width); + end; + end + else + begin + SplitPoint := - Tree.EffectiveOffsetX + FColumns.TotalWidth; + + for I := FColumns.Count - 1 downto 0 do + with TVirtualTreeColumnsCracker(FColumns), Items[PositionToIndex[I]] do + if coVisible in Options then + begin + if IsNearBy(coFixed in Options, SplitterHitTolerance - LeftTolerance, LeftTolerance) then + begin + if CanSplitterResize(P, PositionToIndex[I]) then + begin + Result := True; + TrackIndex := PositionToIndex[I]; + + //Keep the left border of this column. This and the current mouse position + //directly determine the current column width. + FTrackPoint.X := SplitPoint + IfThen(coFixed in Options, Tree.EffectiveOffsetX) - Width; + FTrackPoint.Y := P.Y; + Break; + end; + end; + Dec(SplitPoint, Width); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.DoAfterAutoFitColumn(Column : TColumnIndex); + +begin + if Assigned(Tree.OnAfterAutoFitColumn) then + Tree.OnAfterAutoFitColumn(Self, Column); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.DoAfterColumnWidthTracking(Column : TColumnIndex); + +//Tell the application that a column width tracking operation has been finished. + +begin + if Assigned(Tree.OnAfterColumnWidthTracking) then + Tree.OnAfterColumnWidthTracking(Self, Column); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.DoAfterHeightTracking; + +//Tell the application that a height tracking operation has been finished. + +begin + if Assigned(Tree.OnAfterHeaderHeightTracking) then + Tree.OnAfterHeaderHeightTracking(Self); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.DoBeforeAutoFitColumn(Column : TColumnIndex; SmartAutoFitType : TSmartAutoFitType) : Boolean; + +//Query the application if we may autofit a column. + +begin + Result := True; + if Assigned(Tree.OnBeforeAutoFitColumn) then + Tree.OnBeforeAutoFitColumn(Self, Column, SmartAutoFitType, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.DoBeforeColumnWidthTracking(Column : TColumnIndex; Shift : TShiftState); + +//Tell the a application that a column width tracking operation may begin. + +begin + if Assigned(Tree.OnBeforeColumnWidthTracking) then + Tree.OnBeforeColumnWidthTracking(Self, Column, Shift); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.DoBeforeHeightTracking(Shift : TShiftState); + +//Tell the application that a height tracking operation may begin. + +begin + if Assigned(Tree.OnBeforeHeaderHeightTracking) then + Tree.OnBeforeHeaderHeightTracking(Self, Shift); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.DoCanSplitterResize(P : TPoint; var Allowed : Boolean); +begin + if Assigned(Tree.OnCanSplitterResizeHeader) then + Tree.OnCanSplitterResizeHeader(Self, P, Allowed); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.DoColumnWidthDblClickResize(Column : TColumnIndex; P : TPoint; Shift : TShiftState) : Boolean; + +//Queries the application whether a double click on the column splitter should resize the column. + +begin + Result := True; + if Assigned(Tree.OnColumnWidthDblClickResize) then + Tree.OnColumnWidthDblClickResize(Self, Column, Shift, P, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.DoColumnWidthTracking(Column : TColumnIndex; Shift : TShiftState; var TrackPoint : TPoint; P : TPoint) : Boolean; + +begin + Result := True; + if Assigned(Tree.OnColumnWidthTracking) then + Tree.OnColumnWidthTracking(Self, Column, Shift, TrackPoint, P, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.DoGetPopupMenu(Column : TColumnIndex; Position : TPoint) : TPopupMenu; + +//Queries the application whether there is a column specific header popup menu. + +var + AskParent : Boolean; + +begin + Result := PopupMenu; + if Assigned(Tree.OnGetPopupMenu) then + Tree.OnGetPopupMenu(TBaseVirtualTree(FOwner), nil, Column, Position, AskParent, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.DoHeightTracking(var P : TPoint; Shift : TShiftState) : Boolean; + +begin + Result := True; + if Assigned(Tree.OnHeaderHeightTracking) then + Tree.OnHeaderHeightTracking(Self, P, Shift, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.DoHeightDblClickResize(var P : TPoint; Shift : TShiftState) : Boolean; + +begin + Result := True; + if Assigned(Tree.OnHeaderHeightDblClickResize) then + Tree.OnHeaderHeightDblClickResize(Self, P, Shift, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.DoSetSortColumn(Value : TColumnIndex; pSortDirection : TSortDirection); + +begin + if Value < NoColumn then + Value := NoColumn; + if Value > Columns.Count - 1 then + Value := Columns.Count - 1; + if FSortColumn <> Value then + begin + if FSortColumn > NoColumn then + Invalidate(Columns[FSortColumn]); + FSortColumn := Value; + FSortDirection := pSortDirection; + if FSortColumn > NoColumn then + Invalidate(Columns[FSortColumn]); + if ((toAutoSort in Tree.TreeOptions.AutoOptions) or (hoHeaderClickAutoSort in Options)) and (Tree.UpdateCount = 0) then + Tree.SortTree(FSortColumn, FSortDirection, True); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.DragTo(P : TPoint); + +//Moves the drag image to a new position, which is determined from the passed point P and the previous +//mouse position. + +var + I, NewTarget : Integer; + //optimized drag image move support + ClientP : TPoint; + Left, Right : TDimension; + NeedRepaint : Boolean; //True if the screen needs an update (changed drop target or drop side) + +begin + //Determine new drop target and which side of it is prefered. + ClientP := Tree.ScreenToClient(P); + //Make coordinates relative to (0, 0) of the non-client area. + Inc(ClientP.Y, FHeight); + NewTarget := FColumns.ColumnFromPosition(ClientP); + NeedRepaint := (NewTarget <> InvalidColumn) and (NewTarget <> FColumns.DropTarget); + if NewTarget >= 0 then + begin + FColumns.GetColumnBounds(NewTarget, Left, Right); + if (ClientP.X < Divide((Left + Right), 2)) <> FColumns.DropBefore then + begin + NeedRepaint := True; + FColumns.DropBefore := not FColumns.DropBefore; + end; + end; + + if NeedRepaint then + begin + //Invalidate columns which need a repaint. + if FColumns.DropTarget > NoColumn then + begin + I := FColumns.DropTarget; + FColumns.DropTarget := NoColumn; + Invalidate(FColumns.Items[I]); + end; + if (NewTarget > NoColumn) and (NewTarget <> FColumns.DropTarget) then + begin + Invalidate(FColumns.Items[NewTarget]); + FColumns.DropTarget := NewTarget; + end; + end; + + //Fix for various problems mentioned in issue 248. + if NeedRepaint then + TBaseVirtualTreeCracker(FOwner).UpdateWindow(); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.FixedAreaConstraintsChanged(Sender : TObject); + +//This method gets called when FFixedAreaConstraints is changed. + +begin + if Tree.HandleAllocated then + RescaleHeader + else + Include(FStates, hsNeedScaling); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.GetColumnsClass : TVirtualTreeColumnsClass; + +//Returns the class to be used for the actual column implementation. descendants may optionally override this and +//return their own class. + +begin + Result := TVirtualTreeColumns; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.GetOwner : TPersistent; + +begin + Result := FOwner; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.GetRestoreSelectionColumnIndex : Integer; +begin + if FRestoreSelectionColumnIndex >= 0 then + Result := FRestoreSelectionColumnIndex + else + Result := MainColumn; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.GetShiftState : TShiftState; + +begin + Result := []; + if GetKeyState(VK_SHIFT) < 0 then + Include(Result, ssShift); + if GetKeyState(VK_CONTROL) < 0 then + Include(Result, ssCtrl); + if GetKeyState(VK_MENU) < 0 then + Include(Result, ssAlt); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.HandleHeaderMouseMove(var Message : TWMMouseMove) : Boolean; + +var + P : TPoint; + NextColumn, I : TColumnIndex; + NewWidth : TDimension; + iOffsetX : TDimension; + +begin + Result := False; + with Message do + begin + P := Point(XPos, YPos); + if hsColumnWidthTrackPending in FStates then + begin + Tree.StopTimer(HeaderTimer); + FStates := FStates - [hsColumnWidthTrackPending] + [hsColumnWidthTracking]; + HandleHeaderMouseMove := True; + Result := 0; + end + else if hsHeightTrackPending in FStates then + begin + Tree.StopTimer(HeaderTimer); + FStates := FStates - [hsHeightTrackPending] + [hsHeightTracking]; + HandleHeaderMouseMove := True; + Result := 0; + end + else if hsColumnWidthTracking in FStates then + begin + if DoColumnWidthTracking(FColumns.TrackIndex, GetShiftState, FTrackPoint, P) then + begin + if Tree.UseRightToLeftAlignment then + begin + NewWidth := FTrackPoint.X - XPos; + NextColumn := FColumns.GetPreviousVisibleColumn(FColumns.TrackIndex); + end + else + begin + NewWidth := XPos - FTrackPoint.X; + NextColumn := FColumns.GetNextVisibleColumn(FColumns.TrackIndex); + end; + + iOffsetX := Tree.EffectiveOffsetX; + + // The autosized column cannot be resized using the mouse normally. Instead we resize the next + // visible column, so it look as we directly resize the autosized column. + if (hoAutoResize in FOptions) and (FColumns.TrackIndex = FAutoSizeIndex) and + (NextColumn > NoColumn) and (coResizable in FColumns[NextColumn].Options) and + (FColumns[FColumns.TrackIndex].MinWidth < NewWidth) and + (FColumns[FColumns.TrackIndex].MaxWidth > NewWidth) then + FColumns[NextColumn].Width := FColumns[NextColumn].Width - NewWidth + + FColumns[FColumns.TrackIndex].Width + else + FColumns[FColumns.TrackIndex].Width := NewWidth; // 1 EListError seen here (List index out of bounds (-1)) since 10/2013 + + if (iOffsetX > 0) and (iOffsetX <> Tree.EffectiveOffsetX) then + FTrackPoint.X := FTrackPoint.X + iOffsetX - Tree.EffectiveOffsetX; + end; + HandleHeaderMouseMove := True; + Result := 0; + end + else if hsHeightTracking in FStates then + begin + if DoHeightTracking(P, GetShiftState) then + SetHeight(FHeight + P.Y); + HandleHeaderMouseMove := True; + Result := 0; + end + else + begin + if hsDragPending in FStates then + begin + P := Tree.ClientToScreen(P); + //start actual dragging if allowed + if (hoDrag in FOptions) and Tree.DoHeaderDragging(TVirtualTreeColumnsCracker(FColumns).DownIndex) then + begin + if ((Abs(FDragStart.X - P.X) > Mouse.DragThreshold) or (Abs(FDragStart.Y - P.Y) > Mouse.DragThreshold)) then + begin + Tree.StopTimer(HeaderTimer); + with TVirtualTreeColumnsCracker(FColumns) do + begin + I := DownIndex; + DownIndex := NoColumn; + HoverIndex := NoColumn; + if I > NoColumn then + Invalidate(FColumns[I]); + end; + FStates := FStates - [hsDragPending] + [hsDragging]; + PrepareDrag(P, FDragStart); + HandleHeaderMouseMove := True; + Result := 0; + end; + end; + end + else if hsDragging in FStates then + begin + DragTo(Tree.ClientToScreen(Point(XPos, YPos))); + HandleHeaderMouseMove := True; + Result := 0; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.HandleMessage(var Message : TMessage) : Boolean; + +//The header gets here the opportunity to handle certain messages before they reach the tree. This is important +//because the tree needs to handle various non-client area messages for the header as well as some dragging/tracking +//events. +//By returning True the message will not be handled further, otherwise the message is then dispatched +//to the proper message handlers. + +var + P : TPoint; + I : TColumnIndex; + HitIndex : TColumnIndex; + NewCursor : TVTCursor; + Button : TMouseButton; + IsInHeader, IsHSplitterHit, IsVSplitterHit : Boolean; + + //--------------- local function -------------------------------------------- + + function HSplitterHit : Boolean; + begin + Result := (hoColumnResize in FOptions) and DetermineSplitterIndex(P); + if Result and not InHeader(P) then + begin + // Code commented due to issue #1067. What was the orginal inention of this code? It does not make much sense unless you allow column resize outside the header. + //NextCol := FColumns.GetNextVisibleColumn(FColumns.TrackIndex); + //if not (coFixed in FColumns[FColumns.TrackIndex].Options) or (NextCol <= NoColumn) or + // (coFixed in FColumns[NextCol].Options) or (P.Y > Tree.RangeY) then + Result := False; + end; + end; + +//--------------- end local function ---------------------------------------- + +begin + Result := False; + case Message.Msg of + WM_SIZE : + begin + if not (tsWindowCreating in TBaseVirtualTreeCracker(FOwner).TreeStates) then + if (hoAutoResize in FOptions) and not (hsAutoSizing in FStates) then + begin + TVirtualTreeColumnsCracker(FColumns).AdjustAutoSize(InvalidColumn); + Invalidate(nil); + end + else if not (hsScaling in FStates) then + begin + RescaleHeader; + Invalidate(nil); + end; + end; + CM_PARENTFONTCHANGED : + if FParentFont then + FFont.Assign(TBaseVirtualTreeCracker(FOwner).Font); + CM_BIDIMODECHANGED : + for I := 0 to FColumns.Count - 1 do + if coParentBiDiMode in FColumns[I].Options then + FColumns[I].ParentBiDiModeChanged; + WM_NCMBUTTONDOWN : + begin + with TWMNCMButtonDown(Message) do + P := Tree.ScreenToClient(Point(XCursor, YCursor)); + if InHeader(P) then + TBaseVirtualTreeCracker(FOwner).DoHeaderMouseDown(mbMiddle, GetShiftState, P.X, P.Y + FHeight); + end; + WM_NCMBUTTONUP : + begin + with TWMNCMButtonUp(Message) do + P := FOwner.ScreenToClient(Point(XCursor, YCursor)); + if InHeader(P) then + begin + with TVirtualTreeColumnsCracker(FColumns) do + begin + HandleClick(P, mbMiddle, True, False); + TBaseVirtualTreeCracker(FOwner).DoHeaderMouseUp(TmouseButton.mbMiddle, GetShiftState, P.X, P.Y + Self.FHeight); + DownIndex := NoColumn; + CheckBoxHit := False; + end; + end; + fWasDoubleClick := False; + end; + WM_LBUTTONDBLCLK, WM_NCLBUTTONDBLCLK, WM_NCMBUTTONDBLCLK, WM_NCRBUTTONDBLCLK : + begin + fWasDoubleClick := True; + if Message.Msg <> WM_LBUTTONDBLCLK then + with TWMNCLButtonDblClk(Message) do + P := FOwner.ScreenToClient(Point(XCursor, YCursor)) + else + with TWMLButtonDblClk(Message) do + P := Point(XPos, YPos); + + if (hoHeightDblClickResize in FOptions) and InHeaderSplitterArea(P) and (FDefaultHeight > 0) then + begin + if DoHeightDblClickResize(P, GetShiftState) and (FDefaultHeight > 0) then + SetHeight(FMinHeight); + Result := True; + end + else if HSplitterHit and ((Message.Msg = WM_NCLBUTTONDBLCLK) or (Message.Msg = WM_LBUTTONDBLCLK)) and (hoDblClickResize in FOptions) and (FColumns.TrackIndex > NoColumn) + then + begin + //If the click was on a splitter then resize column to smallest width. + if DoColumnWidthDblClickResize(FColumns.TrackIndex, P, GetShiftState) then + AutoFitColumns(True, smaUseColumnOption, FColumns[FColumns.TrackIndex].Position, FColumns[FColumns.TrackIndex].Position); + Message.Result := 0; + Result := True; + end + else if InHeader(P) and (Message.Msg <> WM_LBUTTONDBLCLK) then + begin + case Message.Msg of + WM_NCMBUTTONDBLCLK : + Button := TMouseButton.mbMiddle; + WM_NCRBUTTONDBLCLK : + Button := TMouseButton.mbRight; + else + //WM_NCLBUTTONDBLCLK + Button := TMouseButton.mbLeft; + end; + if Button = TMouseButton.mbLeft then + TVirtualTreeColumnsCracker(FColumns).AdjustDownColumn(P); + TVirtualTreeColumnsCracker(FColumns).HandleClick(P, Button, True, True); + end; + end; + //The "hot" area of the headers horizontal splitter is partly within the client area of the the tree, so we need + //to handle WM_LBUTTONDOWN here, too. + WM_LBUTTONDOWN, WM_NCLBUTTONDOWN : + begin + + Application.CancelHint; + + if not (csDesigning in Tree.ComponentState) then + begin + with Tree do + begin + //make sure no auto scrolling is active... + StopTimer(ScrollTimer); + DoStateChange([], [tsScrollPending, tsScrolling]); + //... pending editing is cancelled (actual editing remains active) + StopTimer(EditTimer); + DoStateChange([], [tsEditPending]); + end; + end; + + if Message.Msg = WM_LBUTTONDOWN then + //Coordinates are already client area based. + with TWMLButtonDown(Message) do + begin + P := Point(XPos, YPos); + //#909 + FDragStart := Tree.ClientToScreen(P); + end + else + with TWMNCLButtonDown(Message) do + begin + //want the drag start point in screen coordinates + FDragStart := Point(XCursor, YCursor); + P := Tree.ScreenToClient(FDragStart); + end; + + IsInHeader := InHeader(P); + //in design-time header columns are always resizable + if (csDesigning in Tree.ComponentState) then + IsVSplitterHit := InHeaderSplitterArea(P) + else + IsVSplitterHit := InHeaderSplitterArea(P) and CanSplitterResize(P); + IsHSplitterHit := HSplitterHit; + + if IsVSplitterHit or IsHSplitterHit then + begin + FTrackStart := P; + TVirtualTreeColumnsCracker(FColumns).HoverIndex := NoColumn; + if IsVSplitterHit then + begin + if not (csDesigning in Tree.ComponentState) then + DoBeforeHeightTracking(GetShiftState); + Include(FStates, hsHeightTrackPending); + end + else + begin + if not (csDesigning in Tree.ComponentState) then + DoBeforeColumnWidthTracking(FColumns.TrackIndex, GetShiftState); + Include(FStates, hsColumnWidthTrackPending); + end; + + SetCapture(Tree.Handle); + Result := True; + Message.Result := 0; + end + else if IsInHeader then + begin + HitIndex := TVirtualTreeColumnsCracker(FColumns).AdjustDownColumn(P); + //in design-time header columns are always draggable + if ((csDesigning in Tree.ComponentState) and (HitIndex > NoColumn)) or ((hoDrag in FOptions) and (HitIndex > NoColumn) and (coDraggable in FColumns[HitIndex].Options)) + then + begin + //Show potential drag operation. + //Disabled columns do not start a drag operation because they can't be clicked. + Include(FStates, hsDragPending); + SetCapture(Tree.Handle); + Result := True; + Message.Result := 0; + end; + end; + + //This is a good opportunity to notify the application. + if not (csDesigning in Tree.ComponentState) and IsInHeader then + TBaseVirtualTreeCracker(FOwner).DoHeaderMouseDown(TMouseButton.mbLeft, GetShiftState, P.X, P.Y + FHeight); + end; + WM_NCRBUTTONDOWN : + begin + with TWMNCRButtonDown(Message) do + P := FOwner.ScreenToClient(Point(XCursor, YCursor)); + if InHeader(P) then + TBaseVirtualTreeCracker(FOwner).DoHeaderMouseDown(TMouseButton.mbRight, GetShiftState, P.X, P.Y + FHeight); + end; + WM_NCRBUTTONUP : + if not (csDesigning in FOwner.ComponentState) then + with TWMNCRButtonUp(Message) do + begin + Application.CancelHint; + P := FOwner.ScreenToClient(Point(XCursor, YCursor)); + if InHeader(P) then + begin + HandleMessage := TVirtualTreeColumnsCracker(FColumns).HandleClick(P, TMouseButton.mbRight, True, False); + TBaseVirtualTreeCracker(FOwner).DoHeaderMouseUp(TMouseButton.mbRight, GetShiftState, P.X, P.Y + FHeight); + end; + fWasDoubleClick := False; + end; + //When the tree window has an active mouse capture then we only get "client-area" messages. + WM_LBUTTONUP, WM_NCLBUTTONUP : + begin + Application.CancelHint; + + if FStates <> [] then + begin + ReleaseCapture; + if hsDragging in FStates then + begin + //successfull dragging moves columns + with TWMLButtonUp(Message) do + P := Tree.ClientToScreen(Point(XPos, YPos)); + ColumnDropped(P); + end; + Result := True; + Message.Result := 0; + fWasDoubleClick := False; + end; + + case Message.Msg of + WM_LBUTTONUP : + with TWMLButtonUp(Message) do + begin + with TVirtualTreeColumnsCracker(FColumns) do + begin + if DownIndex > NoColumn then + HandleClick(Point(XPos, YPos), TMouseButton.mbLeft, False, False); + end; + if FStates <> [] then + TBaseVirtualTreeCracker(FOwner).DoHeaderMouseUp(TMouseButton.mbLeft, KeysToShiftState(Keys), XPos, YPos); + fWasDoubleClick := False; + end; + WM_NCLBUTTONUP : + begin + with TWMNCLButtonUp(Message) do + P := FOwner.ScreenToClient(Point(XCursor, YCursor)); + if not fWasDoubleClick then + TVirtualTreeColumnsCracker(FColumns).HandleClick(P, TMouseButton.mbLeft, True, False); + TBaseVirtualTreeCracker(FOwner).DoHeaderMouseUp(TMouseButton.mbLeft, GetShiftState, P.X, P.Y + FHeight); + Result := True; + fWasDoubleClick := False; + end; + end; + + if FColumns.TrackIndex > NoColumn then + begin + if hsColumnWidthTracking in FStates then + DoAfterColumnWidthTracking(FColumns.TrackIndex); + Invalidate(Columns[FColumns.TrackIndex]); + FColumns.TrackIndex := NoColumn; + end; + with TVirtualTreeColumnsCracker(FColumns) do + begin + if DownIndex > NoColumn then + begin + Invalidate(FColumns[DownIndex]); + DownIndex := NoColumn; + end; + end; + if hsHeightTracking in FStates then + DoAfterHeightTracking; + + FStates := FStates - [hsDragging, hsDragPending, hsColumnWidthTracking, hsColumnWidthTrackPending, hsHeightTracking, hsHeightTrackPending]; + end; //WM_NCLBUTTONUP + //hovering, mouse leave detection + WM_NCMOUSEMOVE : + with TWMNCMouseMove(Message), TVirtualTreeColumnsCracker(FColumns) do + begin + P := Tree.ScreenToClient(Point(XCursor, YCursor)); + Tree.DoHeaderMouseMove(GetShiftState, P.X, P.Y + FHeight); + if InHeader(P) and ((AdjustHoverColumn(P)) or ((DownIndex >= 0) and (HoverIndex <> DownIndex))) then + begin + //We need a mouse leave detection from here for the non client area. + //TODO: The best solution available would be the TrackMouseEvent API. + //With the drop of the support of Win95 totally and WinNT4 we should replace the timer. + Tree.StopTimer(HeaderTimer); + SetTimer(Tree.Handle, HeaderTimer, 50, nil); + //use Delphi's internal hint handling for header hints too + if hoShowHint in FOptions then + begin + //client coordinates! + XCursor := P.X; + YCursor := P.Y + FHeight; + Application.HintMouseMessage(FOwner, Message); + end; + end; + end; + WM_TIMER : + if TWMTimer(Message).TimerID = HeaderTimer then + begin + //determine current mouse position to check if it left the window + GetCursorPos(P); + P := Tree.ScreenToClient(P); + with TVirtualTreeColumnsCracker(FColumns) do + begin + if not InHeader(P) or ((DownIndex > NoColumn) and (HoverIndex <> DownIndex)) then + begin + Tree.StopTimer(HeaderTimer); + HoverIndex := NoColumn; + ClickIndex := NoColumn; + DownIndex := NoColumn; + CheckBoxHit := False; + Result := True; + Message.Result := 0; + Invalidate(nil); + end; + end; + end; + WM_MOUSEMOVE : //mouse capture and general message redirection + Result := HandleHeaderMouseMove(TWMMouseMove(Message)); + WM_SETCURSOR : + //Feature: design-time header + if (FStates = []) then + begin + //Retrieve last cursor position (GetMessagePos does not work here, I don't know why). + GetCursorPos(P); + + //Is the mouse in the header rectangle and near the splitters? + P := Tree.ScreenToClient(P); + IsHSplitterHit := HSplitterHit; + //in design-time header columns are always resizable + if (csDesigning in Tree.ComponentState) then + IsVSplitterHit := InHeaderSplitterArea(P) + else + IsVSplitterHit := InHeaderSplitterArea(P) and CanSplitterResize(P); + + if IsVSplitterHit or IsHSplitterHit then + begin + NewCursor := Screen.Cursors[Tree.Cursor]; + if IsVSplitterHit and ((hoHeightResize in FOptions) or (csDesigning in Tree.ComponentState)) then + NewCursor := Screen.Cursors[crVSplit] + else if IsHSplitterHit then + NewCursor := Screen.Cursors[crHSplit]; + + if not (csDesigning in Tree.ComponentState) then + Tree.DoGetHeaderCursor(NewCursor); + Result := NewCursor <> Screen.Cursors[crDefault]; + if Result then + begin + WinApi.Windows.SetCursor(NewCursor); + Message.Result := 1; + end; + end; + end + else + begin + Message.Result := 1; + Result := True; + end; + WM_KEYDOWN, WM_KILLFOCUS : + if (Message.Msg = WM_KILLFOCUS) or (TWMKeyDown(Message).CharCode = VK_ESCAPE) then + begin + if hsDragging in FStates then + begin + ReleaseCapture; + FDragImage.EndDrag; + Exclude(FStates, hsDragging); + FColumns.DropTarget := NoColumn; + Invalidate(nil); + Result := True; + Message.Result := 0; + end + else + begin + if [hsColumnWidthTracking, hsHeightTracking] * FStates <> [] then + begin + ReleaseCapture; + if hsColumnWidthTracking in FStates then + DoAfterColumnWidthTracking(FColumns.TrackIndex); + if hsHeightTracking in FStates then + DoAfterHeightTracking; + Result := True; + Message.Result := 0; + end; + + FStates := FStates - [hsColumnWidthTracking, hsColumnWidthTrackPending, hsHeightTracking, hsHeightTrackPending]; + end; + end; + end; +end; + +procedure TVTHeader.ColumnDropped(const P: TPoint); +var + R: TRect; + OldPosition: Integer; +begin + GetWindowRect(Tree.Handle, R); + with FColumns do + begin + FDragImage.EndDrag; + + //Problem fixed: + //Column Header does not paint correctly after a drop in certain conditions + // ** The conditions are, drag is across header, mouse is not moved after + //the drop and the graphics hardware is slow in certain operations (encountered + //on Windows 10). + //Fix for the problem on certain systems where the dropped column header + //does not appear in the new position if the mouse is not moved after + //the drop. The reason is that the restore backup image operation (BitBlt) + //in the above EndDrag is slower than the header repaint in the code below + //and overlaps the new changed header with the older image. + //This happens because BitBlt seems to operate in its own thread in the + //graphics hardware and finishes later than the following code. + // + //To solve this problem, we introduce a small delay here so that the + //changed header in the following code is correctly repainted after + //the delayed BitBlt above has finished operation to restore the old + //backup image. + sleep(50); + + if (DropTarget > - 1) and (DropTarget <> DragIndex) and PtInRect(R, P) then + begin + OldPosition := FColumns[DragIndex].Position; + if FColumns.DropBefore then + begin + if FColumns[DragIndex].Position < FColumns[DropTarget].Position then + FColumns[DragIndex].Position := Max(0, FColumns[DropTarget].Position - 1) + else + FColumns[DragIndex].Position := FColumns[DropTarget].Position; + end + else + begin + if FColumns[DragIndex].Position < FColumns[DropTarget].Position then + FColumns[DragIndex].Position := FColumns[DropTarget].Position + else + FColumns[DragIndex].Position := FColumns[DropTarget].Position + 1; + end; + Tree.DoHeaderDragged(DragIndex, OldPosition); + end + else + Tree.DoHeaderDraggedOut(DragIndex, P); + DropTarget := NoColumn; + FStates := FStates - [hsDragging, hsDragPending]; + end; + Invalidate(nil); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.ImageListChange(Sender : TObject); + +begin + if not (csDestroying in Tree.ComponentState) then + Invalidate(nil); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.PrepareDrag(P, Start : TPoint); + +//Initializes dragging of the header, P is the current mouse postion and Start the initial mouse position. + +var + Image : TBitmap; + HotSpot : TPoint; + DragColumn : TVirtualTreeColumn; + RTLOffset : TDimension; + lDataObject: IDataObject; + lDragEffect: DWord; // The last executed drag effect, not needed here + +begin + //Determine initial position of drag image (screen coordinates). + FColumns.DropTarget := NoColumn; + Start := Tree.ScreenToClient(Start); + Inc(Start.Y, FHeight); + FColumns.DragIndex := FColumns.ColumnFromPosition(Start); + DragColumn := FColumns[FColumns.DragIndex]; + + Image := TBitmap.Create; + with Image do + try + PixelFormat := pf32Bit; + SetSize(DragColumn.Width, FHeight); + + //Erase the entire image with the color key value, for the case not everything + //in the image is covered by the header image. + Canvas.Brush.Color := clBtnFace; + Canvas.FillRect(Rect(0, 0, Width, Height)); + + if Tree.UseRightToLeftAlignment then + RTLOffset := Tree.ComputeRTLOffset + else + RTLOffset := 0; + with DragColumn do + FColumns.PaintHeader(Canvas, Rect(Left, 0, Left + Width, Height), Point( - RTLOffset, 0), RTLOffset); + + //Column rectangles are given in local window coordinates not client coordinates. + HotSpot := Tree.ScreenToClient(P); + HotSpot.X := HotSpot.X - DragColumn.Left - cMargin; + HotSpot.Y := HotSpot.Y + Height - cMargin; // header is in the non-client area and so the coordinates are negative + + if hoRestrictDrag in FOptions then + FDragImage.MoveRestriction := dmrHorizontalOnly + else + FDragImage.MoveRestriction := dmrNone; + + lDataObject := TVTDataObject.Create(Self, TreeView); + FDragImage.PrepareDrag(Image, HotSpot, lDataObject); + SHDoDragDrop(fOwner.Handle, lDataObject, nil, DROPEFFECT_MOVE, lDragEffect); // SHDoDragDrop() supports drag hints and drag images on Windows Vista and later + finally + Image.Free; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.ReadColumns(Reader : TReader); + +begin + Include(FStates, hsLoading); + Columns.Clear; + Reader.ReadValue; + Reader.ReadCollection(Columns); + Exclude(FStates, hsLoading); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.RecalculateHeader; + +//Initiate a recalculation of the non-client area of the owner tree. + +begin + if Tree.HandleAllocated then + begin + Tree.UpdateHeaderRect; + SetWindowPos(Tree.Handle, 0, 0, 0, 0, 0, SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOACTIVATE or SWP_NOOWNERZORDER or SWP_NOSENDCHANGING or SWP_NOSIZE or SWP_NOZORDER); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.RescaleHeader; + +//Rescale the fixed elements (fixed columns, header itself) to FixedAreaConstraints. + +var + FixedWidth, MaxFixedWidth, MinFixedWidth : TDimension; + + //--------------- local function -------------------------------------------- + + procedure ComputeConstraints; + + var + I : TColumnIndex; + + begin + with FColumns do + begin + I := GetFirstVisibleColumn; + while I > NoColumn do + begin + if (coFixed in FColumns[I].Options) and (FColumns[I].Width < FColumns[I].MinWidth) then + TVirtualTreeColumnCracker(FColumns[I]).InternalSetWidth(FColumns[I].MinWidth); //SetWidth has side effects and this bypasses them + I := GetNextVisibleColumn(I); + end; + FixedWidth := GetVisibleFixedWidth; + end; + + with FFixedAreaConstraints do + begin + MinFixedWidth := Divide(Tree.ClientWidth * FMinWidthPercent, 100); + MaxFixedWidth := Divide(Tree.ClientWidth * FMaxWidthPercent, 100); + end; + end; + +//----------- end local function -------------------------------------------- + +begin + if ([csLoading, csReading, csWriting, csDestroying] * Tree.ComponentState = []) and not (hsLoading in FStates) and Tree.HandleAllocated then + begin + Include(FStates, hsScaling); + + SetHeight(FHeight); + RecalculateHeader; + + with FFixedAreaConstraints do + if (FMaxWidthPercent > 0) or (FMinWidthPercent > 0) or (FMinHeightPercent > 0) or (FMaxHeightPercent > 0) then + begin + ComputeConstraints; + + with FColumns do + if (FMaxWidthPercent > 0) and (FixedWidth > MaxFixedWidth) then + ResizeColumns(MaxFixedWidth - FixedWidth, 0, Count - 1, [coVisible, coFixed]) + else if (FMinWidthPercent > 0) and (FixedWidth < MinFixedWidth) then + ResizeColumns(MinFixedWidth - FixedWidth, 0, Count - 1, [coVisible, coFixed]); + + TVirtualTreeColumnsCracker(FColumns).UpdatePositions; + end; + + Exclude(FStates, hsScaling); + Exclude(FStates, hsNeedScaling); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.UpdateMainColumn(); + +//Called once the load process of the owner tree is done. + +begin + if FMainColumn < 0 then + MainColumn := 0; + if FMainColumn > FColumns.Count - 1 then + MainColumn := FColumns.Count - 1; + if (FMainColumn >= 0) and not (coVisible in Self.Columns[FMainColumn].Options) then + begin + //Issue #946: Choose new MainColumn if current one ist not visible + MainColumn := Self.Columns.GetFirstVisibleColumn(); + end +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.UpdateSpringColumns; + +var + I : TColumnIndex; + SpringCount : Integer; + Sign : Integer; + ChangeBy : Single; + Difference : Single; + NewAccumulator : Single; + +begin + with Tree do + ChangeBy := HeaderRect.Right - HeaderRect.Left - FLastWidth; + if (hoAutoSpring in FOptions) and (FLastWidth <> 0) and (ChangeBy <> 0) then + begin + //Stay positive if downsizing the control. + if ChangeBy < 0 then + Sign := - 1 + else + Sign := 1; + ChangeBy := Abs(ChangeBy); + //Count how many columns have spring enabled. + SpringCount := 0; + for I := 0 to FColumns.Count - 1 do + if [coVisible, coAutoSpring] * FColumns[I].Options = [coVisible, coAutoSpring] then + System.Inc(SpringCount); + if SpringCount > 0 then + begin + //Calculate the size to add/sub to each columns. + Difference := ChangeBy / SpringCount; + //Adjust the column's size accumulators and resize if the result is >= 1. + for I := 0 to FColumns.Count - 1 do + if [coVisible, coAutoSpring] * FColumns[I].Options = [coVisible, coAutoSpring] then + begin + //Sum up rest changes from previous runs and the amount from this one and store it in the + //column. If there is at least one pixel difference then do a resize and reset the accumulator. + NewAccumulator := FColumns[I].SpringRest + Difference; + //Set new width if at least one pixel size difference is reached. + if NewAccumulator >= 1 then + TVirtualTreeColumnCracker(FColumns[I]).SetWidth(FColumns[I].Width + (Trunc(NewAccumulator) * Sign)); + FColumns[I].SpringRest := Frac(NewAccumulator); + + //Keep track of the size count. + ChangeBy := ChangeBy - Difference; + //Exit loop if resize count drops below freezing point. + if ChangeBy < 0 then + Break; + end; + end; + end; + with Tree do + FLastWidth := HeaderRect.Right - HeaderRect.Left; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +type + //--- HACK WARNING! + //This type cast is a partial rewrite of the private section of TWriter. The purpose is to have access to + //the FPropPath member, which is otherwise not accessible. The reason why this access is needed is that + //with nested components this member contains unneeded property path information. These information prevent + //successful load of the stored properties later. + //In System.Classes.pas you can see that FPropPath is reset several times to '' to prevent this case for certain properies. + //Unfortunately, there is no clean way for us here to do the same. +{$HINTS off} + TWriterHack = class(TFiler) + private + FRootAncestor : TComponent; + FPropPath : string; + end; +{$HINTS on} + + +procedure TVTHeader.WriteColumns(Writer : TWriter); + +//Write out the columns but take care for the case VT is a nested component. + +var + LastPropPath : string; + +begin + //Save last property path for restoration. + LastPropPath := TWriterHack(Writer).FPropPath; + try + //If VT is a nested component then this path contains the name of the parent component at this time + //(otherwise it is already empty). This path is then combined with the property name under which the tree + //is defined in the parent component. Unfortunately, the load code in System.Classes.pas does not consider this case + //is then unable to load this property. + TWriterHack(Writer).FPropPath := ''; + Writer.WriteCollection(Columns); + finally + TWriterHack(Writer).FPropPath := LastPropPath; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.AllowFocus(ColumnIndex : TColumnIndex) : Boolean; +begin + Result := False; + if not FColumns.IsValidColumn(ColumnIndex) then + Exit; //Just in case. + + Result := (coAllowFocus in FColumns[ColumnIndex].Options); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.AreColumnsStored: Boolean; +begin + // The columns are stored by the owner tree to support Visual Form Inheritance + // GnutGetText skips non-stored properties, so retur Stored True at runtime + Result := not (csDesigning in Self.Treeview.ComponentState); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.Assign(Source : TPersistent); + +begin + if Source is TVTHeader then + begin + AutoSizeIndex := TVTHeader(Source).AutoSizeIndex; + Background := TVTHeader(Source).Background; + Columns := TVTHeader(Source).Columns; + Font := TVTHeader(Source).Font; + FixedAreaConstraints.Assign(TVTHeader(Source).FixedAreaConstraints); + Height := TVTHeader(Source).Height; + Images := TVTHeader(Source).Images; + MainColumn := TVTHeader(Source).MainColumn; + Options := TVTHeader(Source).Options; + ParentFont := TVTHeader(Source).ParentFont; + PopupMenu := TVTHeader(Source).PopupMenu; + SortColumn := TVTHeader(Source).SortColumn; + SortDirection := TVTHeader(Source).SortDirection; + Style := TVTHeader(Source).Style; + + RescaleHeader; + end + else + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.AutoFitColumns(); +begin + AutoFitColumns(not Tree.IsUpdating); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.AutoFitColumns(Animated : Boolean; SmartAutoFitType : TSmartAutoFitType = smaUseColumnOption; RangeStartCol : Integer = NoColumn; + RangeEndCol : Integer = NoColumn); + +//--------------- local functions ------------------------------------------- + + function GetUseSmartColumnWidth(ColumnIndex : TColumnIndex) : Boolean; + + begin + case SmartAutoFitType of + smaAllColumns : + Result := True; + smaUseColumnOption : + Result := coSmartResize in FColumns.Items[ColumnIndex].Options; + else + Result := False; + end; + end; + +//---------------------------------------------------------------------------- + + procedure DoAutoFitColumn(Column : TColumnIndex); + + begin + with TVirtualTreeColumnsCracker(FColumns) do + if ([coResizable, coVisible] * Items[PositionToIndex[Column]].Options = [coResizable, coVisible]) and DoBeforeAutoFitColumn(PositionToIndex[Column], SmartAutoFitType) and + not Tree.OperationCanceled then + begin + if Animated then + AnimatedResize(PositionToIndex[Column], Tree.GetMaxColumnWidth(PositionToIndex[Column], GetUseSmartColumnWidth(PositionToIndex[Column]))) + else + FColumns[PositionToIndex[Column]].Width := Tree.GetMaxColumnWidth(PositionToIndex[Column], GetUseSmartColumnWidth(PositionToIndex[Column])); + + DoAfterAutoFitColumn(PositionToIndex[Column]); + end; + end; + +//--------------- end local functions ---------------------------------------- + +var + I : Integer; + StartCol, EndCol : Integer; + +begin + StartCol := Max(NoColumn + 1, RangeStartCol); + + if RangeEndCol <= NoColumn then + EndCol := FColumns.Count - 1 + else + EndCol := Min(RangeEndCol, FColumns.Count - 1); + + if StartCol > EndCol then + Exit; //nothing to do + + Tree.StartOperation(okAutoFitColumns); + FDoingAutoFitColumns := True; + try + if Assigned(Tree.OnBeforeAutoFitColumns) then + Tree.OnBeforeAutoFitColumns(Self, SmartAutoFitType); + + for I := StartCol to EndCol do + DoAutoFitColumn(I); + + if Assigned(Tree.OnAfterAutoFitColumns) then + Tree.OnAfterAutoFitColumns(Self); + + finally + Tree.EndOperation(okAutoFitColumns); + Tree.Invalidate(); + FDoingAutoFitColumns := False; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.InHeader(P : TPoint) : Boolean; + +//Determines whether the given point (client coordinates!) is within the header rectangle (non-client coordinates). + +var + R, RW : TRect; + +begin + R := Tree.HeaderRect; + + //Current position of the owner in screen coordinates. + GetWindowRect(Tree.Handle, RW); + + //Convert to client coordinates. + MapWindowPoints(0, Tree.Handle, RW, 2); + + //Consider the header within this rectangle. + OffsetRect(R, RW.Left, RW.Top); + Result := PtInRect(R, P); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.InHeaderSplitterArea(P : TPoint) : Boolean; + +//Determines whether the given point (client coordinates!) hits the horizontal splitter area of the header. + +var + R, RW : TRect; + +begin + if (P.Y > 2) or (P.Y < - 2) or not (hoVisible in FOptions) then + Result := False + else + begin + R := Tree.HeaderRect; + Inc(R.Bottom, 2); + + //Current position of the owner in screen coordinates. + GetWindowRect(Tree.Handle, RW); + + //Convert to client coordinates. + MapWindowPoints(0, Tree.Handle, RW, 2); + + //Consider the header within this rectangle. + OffsetRect(R, RW.Left, RW.Top); + Result := PtInRect(R, P); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.InternalSetAutoSizeIndex(const Index : TColumnIndex); +begin + FAutoSizeIndex := index; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.InternalSetMainColumn(const Index : TColumnIndex); +begin + FMainColumn := index; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.InternalSetSortColumn(const Index : TColumnIndex); +begin + FSortColumn := index; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.Invalidate(Column : TVirtualTreeColumn; ExpandToBorder : Boolean = False; UpdateNowFlag : Boolean = False); + +//Because the header is in the non-client area of the tree it needs some special handling in order to initiate its +//repainting. +//If ExpandToBorder is True then not only the given column but everything or (depending on hoFullRepaintOnResize) just +//everything to its right (or left, in RTL mode) will be invalidated (useful for resizing). This makes only sense when +//a column is given. + +var + R, RW : TRect; + Flags : Cardinal; + +begin + if (hoVisible in FOptions) and Tree.HandleAllocated then + with Tree do + begin + if Column = nil then + R := HeaderRect + else + begin + R := Column.GetRect; + if not (coFixed in Column.Options) then + OffsetRect(R, - EffectiveOffsetX, 0); + if UseRightToLeftAlignment then + OffsetRect(R, ComputeRTLOffset, 0); + if ExpandToBorder then + begin + if (hoFullRepaintOnResize in Header.Options) then + begin + R.Left := HeaderRect.Left; + R.Right := HeaderRect.Right; + end + else + begin + if UseRightToLeftAlignment then + R.Left := HeaderRect.Left + else + R.Right := HeaderRect.Right; + end; + end; + end; + R.Bottom := Tree.ClientHeight; //We want to repaint the entire column to bottom, not just the header + + //Current position of the owner in screen coordinates. + GetWindowRect(Handle, RW); + + //Consider the header within this rectangle. + OffsetRect(R, RW.Left, RW.Top); + + //Expressed in client coordinates (because RedrawWindow wants them so, they will actually become negative). + MapWindowPoints(0, Handle, R, 2); + Flags := RDW_FRAME or RDW_INVALIDATE or RDW_VALIDATE or RDW_NOINTERNALPAINT or RDW_NOERASE or RDW_NOCHILDREN; + if UpdateNowFlag then + Flags := Flags or RDW_UPDATENOW; + RedrawWindow(@R, 0, Flags); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.LoadFromStream(const Stream : TStream); + +//restore the state of the header from the given stream + +var + Dummy, Version : Integer; + S : AnsiString; + OldOptions : TVTHeaderOptions; + +begin + Include(FStates, hsLoading); + with Stream do + try + //Switch off all options which could influence loading the columns (they will be later set again). + OldOptions := FOptions; + FOptions := []; + + //Determine whether the stream contains data without a version number. + ReadBuffer(Dummy, SizeOf(Dummy)); + if Dummy > - 1 then + begin + //Seek back to undo the read operation if this is an old stream format. + Seek( - SizeOf(Dummy), soFromCurrent); + Version := - 1; + end + else //Read version number if this is a "versionized" format. + ReadBuffer(Version, SizeOf(Version)); + Columns.LoadFromStream(Stream, Version); + + ReadBuffer(Dummy, SizeOf(Dummy)); + AutoSizeIndex := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + Background := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + Height := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + FOptions := OldOptions; + Options := TVTHeaderOptions(Dummy); + //PopupMenu is neither saved nor restored + ReadBuffer(Dummy, SizeOf(Dummy)); + Style := TVTHeaderStyle(Dummy); + //TFont has no own save routine so we do it manually + with Font do + begin + ReadBuffer(Dummy, SizeOf(Dummy)); + Color := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + Height := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + SetLength(S, Dummy); + ReadBuffer(PAnsiChar(S)^, Dummy); + Name := UTF8ToString(S); + ReadBuffer(Dummy, SizeOf(Dummy)); + Pitch := TFontPitch(Dummy); + ReadBuffer(Dummy, SizeOf(Dummy)); + Style := TFontStyles(Byte(Dummy)); + end; + + //Read data introduced by stream version 1+. + if Version > 0 then + begin + ReadBuffer(Dummy, SizeOf(Dummy)); + MainColumn := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + SortColumn := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + SortDirection := TSortDirection(Byte(Dummy)); + end; + + //Read data introduced by stream version 5+. + if Version > 4 then + begin + ReadBuffer(Dummy, SizeOf(Dummy)); + ParentFont := Boolean(Dummy); + ReadBuffer(Dummy, SizeOf(Dummy)); + FMaxHeight := Integer(Dummy); + ReadBuffer(Dummy, SizeOf(Dummy)); + FMinHeight := Integer(Dummy); + ReadBuffer(Dummy, SizeOf(Dummy)); + FDefaultHeight := Integer(Dummy); + with FFixedAreaConstraints do + begin + ReadBuffer(Dummy, SizeOf(Dummy)); + FMaxHeightPercent := TVTConstraintPercent(Dummy); + ReadBuffer(Dummy, SizeOf(Dummy)); + FMaxWidthPercent := TVTConstraintPercent(Dummy); + ReadBuffer(Dummy, SizeOf(Dummy)); + FMinHeightPercent := TVTConstraintPercent(Dummy); + ReadBuffer(Dummy, SizeOf(Dummy)); + FMinWidthPercent := TVTConstraintPercent(Dummy); + end; + end; + finally + Exclude(FStates, hsLoading); + RecalculateHeader(); + Tree.DoColumnResize(NoColumn); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVTHeader.ResizeColumns(ChangeBy : TDimension; RangeStartCol : TColumnIndex; RangeEndCol : TColumnIndex; Options : TVTColumnOptions = [coVisible]) : TDimension; + +//Distribute the given width change to a range of columns. A 'fair' way is used to distribute ChangeBy to the columns, +//while ensuring that everything that can be distributed will be distributed. + +var + Start, I : TColumnIndex; + ColCount, + Sign: Integer; + ToGo, MaxDelta, Difference, Rest: TDimension; + Constraints, Widths : array of TDimension; + BonusPixel : Boolean; + + //--------------- local functions ------------------------------------------- + + function IsResizable(Column : TColumnIndex) : Boolean; + + begin + if BonusPixel then + Result := Widths[Column - RangeStartCol] < Constraints[Column - RangeStartCol] + else + Result := Widths[Column - RangeStartCol] > Constraints[Column - RangeStartCol]; + end; + +//--------------------------------------------------------------------------- + + procedure IncDelta(Column : TColumnIndex); + + begin + if BonusPixel then + Inc(MaxDelta, FColumns[Column].MaxWidth - Widths[Column - RangeStartCol]) + else + Inc(MaxDelta, Widths[Column - RangeStartCol] - Constraints[Column - RangeStartCol]); + end; + +//--------------------------------------------------------------------------- + + function ChangeWidth(Column : TColumnIndex; Delta : TDimension) : TDimension; + + begin + if Delta > 0 then + Delta := Min(Delta, Constraints[Column - RangeStartCol] - Widths[Column - RangeStartCol]) + else + Delta := Max(Delta, Constraints[Column - RangeStartCol] - Widths[Column - RangeStartCol]); + + Inc(Widths[Column - RangeStartCol], Delta); + Dec(ToGo, Abs(Delta)); + Result := Abs(Delta); + end; + +//--------------------------------------------------------------------------- + + function ReduceConstraints : Boolean; + + var + MaxWidth: TDimension; + MaxReserveCol, Column : TColumnIndex; + + begin + Result := True; + if not (hsScaling in FStates) or BonusPixel then + Exit; + + MaxWidth := 0; + MaxReserveCol := NoColumn; + for Column := RangeStartCol to RangeEndCol do + if (Options * FColumns[Column].Options = Options) and (FColumns[Column].Width > MaxWidth) then + begin + MaxWidth := Widths[Column - RangeStartCol]; + MaxReserveCol := Column; + end; + + if (MaxReserveCol <= NoColumn) or (Constraints[MaxReserveCol - RangeStartCol] <= 10) then + Result := False + else + Dec(Constraints[MaxReserveCol - RangeStartCol], Divide(Constraints[MaxReserveCol - RangeStartCol], 10)); + end; + +//----------- end local functions ------------------------------------------- + +begin + Result := 0; + if (ChangeBy <> 0) and (RangeEndCol >= 0) then // RangeEndCol == -1 means no columns, so nothing to do + begin + //Do some initialization here + BonusPixel := ChangeBy > 0; + Sign := IfThen(BonusPixel, 1, - 1); + Start := IfThen(BonusPixel, RangeStartCol, RangeEndCol); + ToGo := Abs(ChangeBy); + SetLength(Widths, RangeEndCol - RangeStartCol + 1); + SetLength(Constraints, RangeEndCol - RangeStartCol + 1); + for I := RangeStartCol to RangeEndCol do + begin + Widths[I - RangeStartCol] := FColumns[I].Width; + Constraints[I - RangeStartCol] := IfThen(BonusPixel, FColumns[I].MaxWidth, FColumns[I].MinWidth); + end; + + repeat + repeat + MaxDelta := 0; + ColCount := 0; + for I := RangeStartCol to RangeEndCol do + if (Options * FColumns[I].Options = Options) and IsResizable(I) then + begin + System.Inc(ColCount); + IncDelta(I); + end; + if MaxDelta < Abs(ChangeBy) then + if not ReduceConstraints then + Break; + until (MaxDelta >= Abs(ChangeBy)) or not (hsScaling in FStates); + + if ColCount = 0 then + Break; + + ToGo := Min(ToGo, MaxDelta); + Difference := ToGo div ColCount; + Rest := ToGo mod ColCount; + + if Difference > 0 then + for I := RangeStartCol to RangeEndCol do + if (Options * FColumns[I].Options = Options) and IsResizable(I) then + ChangeWidth(I, Difference * Sign); + + //Now distribute Rest. + I := Start; + while Rest > 0 do + begin + if (Options * FColumns[I].Options = Options) and IsResizable(I) then + if FColumns[I].BonusPixel <> BonusPixel then + begin + Dec(Rest, ChangeWidth(I, Sign)); + FColumns[I].BonusPixel := BonusPixel; + end; + System.Inc(I, Sign); + if (BonusPixel and (I > RangeEndCol)) or (not BonusPixel and (I < RangeStartCol)) then + begin + for I := RangeStartCol to RangeEndCol do + if Options * FColumns[I].Options = Options then + FColumns[I].BonusPixel := not FColumns[I].BonusPixel; + I := Start; + end; + end; + until ToGo <= 0; + + //Now set the computed widths. We also compute the result here. + Include(FStates, hsResizing); + for I := RangeStartCol to RangeEndCol do + if (Options * FColumns[I].Options = Options) then + begin + Inc(Result, Widths[I - RangeStartCol] - FColumns[I].Width); + TVirtualTreeColumnCracker(FColumns[I]).SetWidth(Widths[I - RangeStartCol]); + end; + Exclude(FStates, hsResizing); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.RestoreColumns; + +//Restores all columns to their width which they had before they have been auto fitted. + +var + I : TColumnIndex; + +begin + with TVirtualTreeColumnsCracker(FColumns) do + for I := Count - 1 downto 0 do + if [coResizable, coVisible] * Items[PositionToIndex[I]].Options = [coResizable, coVisible] then + Items[I].RestoreLastWidth; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.ToggleSortDirection; +// Toggles the current sorting direction +begin + if SortDirection = sdDescending then + SortDirection := sdAscending + else + SortDirection := sdDescending; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVTHeader.SaveToStream(const Stream : TStream); + +//Saves the complete state of the header into the provided stream. + +var + Dummy : Integer; + DummyDimension: TDimension; + Tmp : AnsiString; + +begin + with Stream do + begin + //In previous version of VT was no header stream version defined. + //For feature enhancements it is necessary, however, to know which stream + //format we are trying to load. + //In order to distict from non-version streams an indicator is inserted. + Dummy := - 1; + WriteBuffer(Dummy, SizeOf(Dummy)); + //Write current stream version number, nothing more is required at the time being. + Dummy := VTHeaderStreamVersion; + WriteBuffer(Dummy, SizeOf(Dummy)); + + //Save columns in case they depend on certain options (like auto size). + Columns.SaveToStream(Stream); + + Dummy := FAutoSizeIndex; + WriteBuffer(Dummy, SizeOf(Dummy)); + Dummy := FBackgroundColor; + WriteBuffer(Dummy, SizeOf(Dummy)); + DummyDimension:= FHeight; + WriteBuffer(DummyDimension, SizeOf(DummyDimension)); + Dummy := Integer(FOptions); + WriteBuffer(Dummy, SizeOf(Dummy)); + //PopupMenu is neither saved nor restored + Dummy := Ord(FStyle); + WriteBuffer(Dummy, SizeOf(Dummy)); + //TFont has no own save routine so we do it manually + with Font do + begin + Dummy := Color; + WriteBuffer(Dummy, SizeOf(Dummy)); + + //Need only to write one: size or height, I decided to write height. + DummyDimension := Height; + WriteBuffer(DummyDimension, SizeOf(DummyDimension)); + Tmp := UTF8Encode(Name); + Dummy := Length(Tmp); + WriteBuffer(Dummy, SizeOf(Dummy)); + WriteBuffer(PAnsiChar(Tmp)^, Dummy); + Dummy := Ord(Pitch); + WriteBuffer(Dummy, SizeOf(Dummy)); + Dummy := Byte(Style); + WriteBuffer(Dummy, SizeOf(Dummy)); + end; + + //Data introduced by stream version 1. + Dummy := FMainColumn; + WriteBuffer(Dummy, SizeOf(Dummy)); + Dummy := FSortColumn; + WriteBuffer(Dummy, SizeOf(Dummy)); + Dummy := Byte(FSortDirection); + WriteBuffer(Dummy, SizeOf(Dummy)); + + //Data introduced by stream version 5. + Dummy := Integer(ParentFont); + WriteBuffer(Dummy, SizeOf(Dummy)); + DummyDimension := FMaxHeight; + WriteBuffer(DummyDimension, SizeOf(DummyDimension)); + DummyDimension := FMinHeight; + WriteBuffer(DummyDimension, SizeOf(DummyDimension)); + DummyDimension := FDefaultHeight; + WriteBuffer(DummyDimension, SizeOf(DummyDimension)); + + with FFixedAreaConstraints do + begin + Dummy := Integer(FMaxHeightPercent); + WriteBuffer(Dummy, SizeOf(Dummy)); + Dummy := Integer(FMaxWidthPercent); + WriteBuffer(Dummy, SizeOf(Dummy)); + Dummy := Integer(FMinHeightPercent); + WriteBuffer(Dummy, SizeOf(Dummy)); + Dummy := Integer(FMinWidthPercent); + WriteBuffer(Dummy, SizeOf(Dummy)); + end; + end; +end; + +{ TVTHeaderHelper } + +function TVTHeaderHelper.Tree : TBaseVirtualTreeCracker; +begin + Result := TBaseVirtualTreeCracker(Self.FOwner); +end; + + +//----------------- TVirtualTreeColumn --------------------------------------------------------------------------------- + +constructor TVirtualTreeColumn.Create(Collection : TCollection); + +begin + FMinWidth := 10; + FMaxWidth := 10000; + FImageIndex := - 1; + FMargin := 4; + FSpacing := cDefaultColumnSpacing; + FText := ''; + FOptions := DefaultColumnOptions; + FAlignment := taLeftJustify; + FBiDiMode := bdLeftToRight; + FColor := clWindow; + FLayout := blGlyphLeft; + FBonusPixel := False; + FCaptionAlignment := taLeftJustify; + FCheckType := ctCheckBox; + FCheckState := csUncheckedNormal; + FCheckBox := False; + FHasImage := False; + FDefaultSortDirection := sdAscending; + FEditNextColumn := - 1; + + inherited Create(Collection); + + if Assigned(Owner) then + begin + FWidth := Owner.DefaultWidth; + FLastWidth := Owner.DefaultWidth; + FPosition := Owner.Count - 1; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetCollection(Value : TCollection); +begin + inherited; + // Read parent bidi mode and color values as default values. + ParentBiDiModeChanged; + ParentColorChanged; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +destructor TVirtualTreeColumn.Destroy; + +var + I : Integer; + ai : TColumnIndex; + sc : TColumnIndex; + + //--------------- local function --------------------------------------------- + + procedure AdjustColumnIndex(var ColumnIndex : TColumnIndex); + + begin + if Index = ColumnIndex then + ColumnIndex := NoColumn + else + if Index < ColumnIndex then + System.Dec(ColumnIndex); + end; + + //--------------- end local function ----------------------------------------- + +begin + // Check if this column is somehow referenced by its collection parent or the header. + with Owner do + begin + // If the columns collection object is currently deleting all columns + // then we don't need to check the various cached indices individually. + if not FClearing then + begin + TreeViewControl.CancelEditNode; + IndexChanged(Index, - 1); + + AdjustColumnIndex(FHoverIndex); + AdjustColumnIndex(FDownIndex); + AdjustColumnIndex(FTrackIndex); + AdjustColumnIndex(FClickIndex); + + with Header do + begin + ai := AutoSizeIndex; + AdjustColumnIndex(ai); + InternalSetAutoSizeIndex(ai); + if Index = MainColumn then + begin + // If the current main column is about to be destroyed then we have to find a new main column. + InternalSetMainColumn(NoColumn); //SetColumn has side effects we want to avoid here. + for I := 0 to Count - 1 do + if I <> Index then + begin + InternalSetMainColumn(I); + Break; + end; + end; + sc := SortColumn; + AdjustColumnIndex(sc); + InternalSetSortColumn(sc); + end; + end; + end; + + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.GetCaptionAlignment : TAlignment; + +begin + if coUseCaptionAlignment in FOptions then + Result := FCaptionAlignment + else + Result := FAlignment; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.GetCaptionWidth : TDimension; +var + Theme : HTHEME; + AdvancedOwnerDraw : Boolean; + PaintInfo : THeaderPaintInfo; + RequestedElements : THeaderPaintElements; + + TextSize : TSize; + HeaderGlyphSize : TPoint; + UseText : Boolean; + R : TRect; +begin + AdvancedOwnerDraw := (hoOwnerDraw in Header.Options) and Assigned(TreeViewControl.OnAdvancedHeaderDraw) and Assigned(TreeViewControl.OnHeaderDrawQueryElements) and + not (csDesigning in TreeViewControl.ComponentState); + + PaintInfo.Column := Self; + PaintInfo.TargetCanvas := Owner.HeaderBitmap.Canvas; + PaintInfo.TargetCanvas.Font := Header.Font; + + with PaintInfo, Column do + begin + ShowHeaderGlyph := (hoShowImages in Header.Options) and ((Assigned(Header.Images) and (FImageIndex > - 1)) or FCheckBox); + ShowSortGlyph := ((Header.SortColumn > - 1) and (Self = Owner.Items[Header.SortColumn])) and (hoShowSortGlyphs in Header.Options); + + // This path for text columns or advanced owner draw. + // See if the application wants to draw part of the header itself. + RequestedElements := []; + if AdvancedOwnerDraw then + begin + PaintInfo.Column := Self; + TreeViewControl.DoHeaderDrawQueryElements(PaintInfo, RequestedElements); + end; + end; + + UseText := Length(FText) > 0; + // If nothing is to show then don't waste time with useless preparation. + if not (UseText or PaintInfo.ShowHeaderGlyph or PaintInfo.ShowSortGlyph) then + Exit(0); + + // Calculate sizes of the involved items. + with Header do + begin + if PaintInfo.ShowHeaderGlyph then + if not FCheckBox then + begin + if Assigned(Images) then + HeaderGlyphSize := Point(Images.Width, Images.Height); + end + else + with Self.TreeViewControl do + begin + if Assigned(CheckImages) then + HeaderGlyphSize := Point(CheckImages.Width, CheckImages.Height); + end + else + HeaderGlyphSize := Point(0, 0); + if PaintInfo.ShowSortGlyph then + begin + if tsUseExplorerTheme in Self.TreeViewControl.TreeStates then + begin + R := Rect(0, 0, 100, 100); + Theme := OpenThemeData(Self.TreeViewControl.Handle, 'HEADER'); + GetThemePartSize(Theme, PaintInfo.TargetCanvas.Handle, HP_HEADERSORTARROW, HSAS_SORTEDUP, @R, TS_TRUE, PaintInfo.SortGlyphSize); + CloseThemeData(Theme); + end + else + begin + PaintInfo.SortGlyphSize.cx := Self.TreeViewControl.ScaledPixels(16); + PaintInfo.SortGlyphSize.cy := Self.TreeViewControl.ScaledPixels(4); + end; + end + else + begin + PaintInfo.SortGlyphSize.cx := 0; + PaintInfo.SortGlyphSize.cy := 0; + end; + end; + + if UseText then + begin + GetTextExtentPoint32W(PaintInfo.TargetCanvas.Handle, PWideChar(FText), Length(FText), TextSize); + Inc(TextSize.cx, 2); + end + else + begin + TextSize.cx := 0; + TextSize.cy := 0; + end; + + // if CalculateTextRect then + Result := TextSize.cx; + if PaintInfo.ShowHeaderGlyph then + if Layout in [blGlyphLeft, blGlyphRight] then + Inc(Result, HeaderGlyphSize.X + FSpacing) + else // if Layout in [ blGlyphTop, blGlyphBottom] then + Result := Max(Result, HeaderGlyphSize.X); + if PaintInfo.ShowSortGlyph then + Inc(Result, PaintInfo.SortGlyphSize.cx + FSpacing + 2); // without this +2, there is a slight movement of the sort glyph when expanding the column + +end; +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.GetLeft : TDimension; + +begin + Result := FLeft; + if [coVisible, coFixed] * FOptions <> [coVisible, coFixed] then + Dec(Result, TreeViewControl.EffectiveOffsetX); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.IsBiDiModeStored : Boolean; + +begin + Result := not (coParentBidiMode in FOptions); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.IsCaptionAlignmentStored : Boolean; + +begin + Result := coUseCaptionAlignment in FOptions; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.IsColorStored : Boolean; + +begin + Result := not (coParentColor in FOptions); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetAlignment(const Value : TAlignment); + +begin + if FAlignment <> Value then + begin + FAlignment := Value; + Changed(False); + // Setting the alignment affects also the tree, hence invalidate it too. + TreeViewControl.Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetBiDiMode(Value : TBiDiMode); + +begin + if Value <> FBiDiMode then + begin + FBiDiMode := Value; + Exclude(FOptions, coParentBidiMode); + Changed(False); + // Setting the alignment affects also the tree, hence invalidate it too. + TreeViewControl.Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetCaptionAlignment(const Value : TAlignment); + +begin + if not (coUseCaptionAlignment in FOptions) or (FCaptionAlignment <> Value) then + begin + FCaptionAlignment := Value; + Include(FOptions, coUseCaptionAlignment); + // Setting the alignment affects also the tree, hence invalidate it too. + Header.Invalidate(Self); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetColor(const Value : TColor); + +begin + if FColor <> Value then + begin + FColor := Value; + Exclude(FOptions, coParentColor); + Exclude(FOptions, coStyleColor); // Issue #919 + Changed(False); + TreeViewControl.Invalidate; + end; +end; + +function TVirtualTreeColumn.GetEffectiveColor() : TColor; +// Returns the color that should effectively be used as background color for this +// column considering all flags in the TVirtualTreeColumn.Options property +begin + if (coParentColor in Options) or ((coStyleColor in Options) and TreeViewControl.VclStyleEnabled) then + Result := TreeViewControl.Colors.BackGroundColor + else + Result := Self.Color; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetCheckBox(Value : Boolean); + +begin + if Value <> FCheckBox then + begin + FCheckBox := Value; + if Value and (csDesigning in TreeViewControl.ComponentState) then + Header.Options := Header.Options + [hoShowImages]; + Changed(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetCheckState(Value : TCheckState); + +begin + if Value <> FCheckState then + begin + FCheckState := Value; + Changed(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetCheckType(Value : TCheckType); + +begin + if Value <> FCheckType then + begin + FCheckType := Value; + Changed(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetImageIndex(Value : TImageIndex); + +begin + if Value <> FImageIndex then + begin + FImageIndex := Value; + Changed(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetLayout(Value : TVTHeaderColumnLayout); + +begin + if FLayout <> Value then + begin + FLayout := Value; + Changed(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetMargin(Value : TDimension); + +begin + // Compatibility setting for -1. + if Value < 0 then + Value := 4; + if FMargin <> Value then + begin + FMargin := Value; + Changed(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetMaxWidth(Value : TDimension); + +begin + if Value < FMinWidth then + Value := FMinWidth; + FMaxWidth := Value; + SetWidth(FWidth); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetMinWidth(Value : TDimension); + +begin + if Value < 0 then + Value := 0; + if Value > FMaxWidth then + Value := FMaxWidth; + FMinWidth := Value; + SetWidth(FWidth); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetOptions(Value : TVTColumnOptions); + +var + ToBeSet, + ToBeCleared : TVTColumnOptions; + VisibleChanged, + lParentColorSet : Boolean; +begin + if FOptions <> Value then + begin + ToBeCleared := FOptions - Value; + ToBeSet := Value - FOptions; + + FOptions := Value; + if coFixed in ToBeSet then + FOptions := FOptions - [coDraggable]; // issue #1314 + + VisibleChanged := coVisible in (ToBeSet + ToBeCleared); + lParentColorSet := coParentColor in ToBeSet; + + if coParentBidiMode in ToBeSet then + ParentBiDiModeChanged; + if lParentColorSet then + begin + Include(FOptions, coStyleColor); // Issue #919 + ParentColorChanged(); + end; + + if coAutoSpring in ToBeSet then + FSpringRest := 0; + + if coVisible in ToBeCleared then + Header.UpdateMainColumn(); // Fixes issue #946 + + if ((coFixed in ToBeSet) or (coFixed in ToBeCleared)) and (coVisible in FOptions) then + Header.RescaleHeader; + + Changed(False); + // Need to repaint and adjust the owner tree too. + if not (csLoading in TreeViewControl.ComponentState) and (VisibleChanged or lParentColorSet) and (Owner.UpdateCount = 0) and TreeViewControl.HandleAllocated then + begin + TreeViewControl.Invalidate(); + if VisibleChanged then + begin + TreeViewControl.DoColumnVisibilityChanged(Self.Index, coVisible in ToBeSet); + TreeViewControl.UpdateHorizontalScrollBar(False); + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetPosition(Value : TColumnPosition); + +var + Temp : TColumnIndex; + +begin + if (csLoading in TreeViewControl.ComponentState) or (Owner.UpdateCount > 0) then + // Only cache the position for final fixup when loading from DFM. + FPosition := Value + else + begin + if Value >= TColumnPosition(Collection.Count) then + Value := Collection.Count - 1; + if FPosition <> Value then + begin + with Owner do + begin + InitializePositionArray; + TreeViewControl.CancelEditNode; + AdjustPosition(Self, Value); + Self.Changed(False); + + // Need to repaint. + with Self.Header do + begin + if (UpdateCount = 0) and TreeViewControl.HandleAllocated then + begin + Invalidate(Self); + TreeViewControl.Invalidate; + end; + end; + end; + + // If the moved column is now within the fixed columns then we make it fixed as well. If it's not + // we clear the fixed state (in case that fixed column is moved outside fixed area). + if (coFixed in FOptions) and (FPosition > 0) then + Temp := Owner.ColumnFromPosition(FPosition - 1) + else + Temp := Owner.ColumnFromPosition(FPosition + 1); + + if Temp <> NoColumn then + begin + if coFixed in Owner[Temp].Options then + Options := Options + [coFixed] + else + Options := Options - [coFixed]; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetSpacing(Value : TDimension); + +begin + if FSpacing <> Value then + begin + FSpacing := Value; + Changed(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetStyle(Value : TVirtualTreeColumnStyle); + +begin + if FStyle <> Value then + begin + FStyle := Value; + Changed(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetText(const Value : string); + +begin + if FText <> Value then + begin + FText := Value; + FCaptionText := ''; + Changed(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SetWidth(Value : TDimension); + +var + EffectiveMaxWidth, + EffectiveMinWidth, + TotalFixedMaxWidth, + TotalFixedMinWidth : TDimension; + I : TColumnIndex; + +begin + if not (hsScaling in Header.States) then + if ([coVisible, coFixed] * FOptions = [coVisible, coFixed]) then + begin + with Header, FixedAreaConstraints, TreeViewControl do + begin + TotalFixedMinWidth := 0; + TotalFixedMaxWidth := 0; + for I := 0 to Columns.Count - 1 do + if ([coVisible, coFixed] * Columns[I].Options = [coVisible, coFixed]) then + begin + Inc(TotalFixedMaxWidth, Columns[I].MaxWidth); + Inc(TotalFixedMinWidth, Columns[I].MinWidth); + end; + + if HandleAllocated then // Prevent premature creation of window handle, see issue #1073 + begin + // The percentage values have precedence over the pixel values. + If MaxWidthPercent > 0 then + TotalFixedMinWidth := Min(Divide(ClientWidth * MaxWidthPercent, 100), TotalFixedMinWidth); + If MinWidthPercent > 0 then + TotalFixedMaxWidth := Max(Divide(ClientWidth * MinWidthPercent, 100), TotalFixedMaxWidth); + + EffectiveMaxWidth := Min(TotalFixedMaxWidth - (Columns.GetVisibleFixedWidth - Self.FWidth), FMaxWidth); + EffectiveMinWidth := Max(TotalFixedMinWidth - (Columns.GetVisibleFixedWidth - Self.FWidth), FMinWidth); + Value := Min(Max(Value, EffectiveMinWidth), EffectiveMaxWidth); + + if MinWidthPercent > 0 then + Value := Max(Divide(ClientWidth * MinWidthPercent, 100) - Columns.GetVisibleFixedWidth + Self.FWidth, Value); + if MaxWidthPercent > 0 then + Value := Min(Divide(ClientWidth * MaxWidthPercent, 100) - Columns.GetVisibleFixedWidth + Self.FWidth, Value); + end;// if HandleAllocated + end; + end + else + Value := Min(Max(Value, FMinWidth), FMaxWidth); + + if FWidth <> Value then + begin + FLastWidth := FWidth; + if not (hsResizing in Header.States) then + FBonusPixel := False; + if not (hoAutoResize in Header.Options) or (Index <> Header.AutoSizeIndex) then + begin + FWidth := Value; + Owner.UpdatePositions; + end; + if not (csLoading in TreeViewControl.ComponentState) and (TreeViewControl.UpdateCount = 0) then + begin + if hoAutoResize in Header.Options then + Owner.AdjustAutoSize(Index); + TreeViewControl.DoColumnResize(Index); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.ChangeScale(M, D : TDimension); +begin + FMinWidth := MulDiv(FMinWidth, M, D); + FMaxWidth := MulDiv(FMaxWidth, M, D); + FSpacing := MulDiv(FSpacing, M, D); + Self.Width := MulDiv(Self.Width, M, D); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.ComputeHeaderLayout(var PaintInfo : THeaderPaintInfo; DrawFormat : Cardinal; CalculateTextRect : Boolean = False); + +// The layout of a column header is determined by a lot of factors. This method takes them all into account and +// determines all necessary positions and bounds: +// - for the header text +// - the header glyph +// - the sort glyph + +var + TextSize : TSize; + TextPos, + ClientSize, + HeaderGlyphSize : TPoint; + CurrentAlignment : TAlignment; + MinLeft, + MaxRight, + TextSpacing : TDimension; + UseText : Boolean; + R : TRect; + Theme : HTHEME; + +begin + UseText := Length(FText) > 0; + // If nothing is to show then don't waste time with useless preparation. + if not (UseText or PaintInfo.ShowHeaderGlyph or PaintInfo.ShowSortGlyph) then + Exit; + + CurrentAlignment := CaptionAlignment; + if FBiDiMode <> bdLeftToRight then + ChangeBiDiModeAlignment(CurrentAlignment); + + // Calculate sizes of the involved items. + ClientSize := Point(PaintInfo.PaintRectangle.Right - PaintInfo.PaintRectangle.Left, PaintInfo.PaintRectangle.Bottom - PaintInfo.PaintRectangle.Top); + with Owner, Header do + begin + if PaintInfo.ShowHeaderGlyph then + if not FCheckBox then + HeaderGlyphSize := Point(Images.Width, Images.Height) + else + with Self.TreeViewControl do + begin + if Assigned(CheckImages) then + HeaderGlyphSize := Point(CheckImages.Width, CheckImages.Height); + end + else + HeaderGlyphSize := Point(0, 0); + if PaintInfo.ShowSortGlyph then + begin + if tsUseExplorerTheme in Self.TreeViewControl.TreeStates then + begin + R := Rect(0, 0, 100, 100); + Theme := OpenThemeData(TreeViewControl.Handle, 'HEADER'); + GetThemePartSize(Theme, PaintInfo.TargetCanvas.Handle, HP_HEADERSORTARROW, HSAS_SORTEDUP, @R, TS_TRUE, PaintInfo.SortGlyphSize); + CloseThemeData(Theme); + end + else + begin + PaintInfo.SortGlyphSize.cx := Self.TreeViewControl.ScaledPixels(16); + PaintInfo.SortGlyphSize.cy := Self.TreeViewControl.ScaledPixels(4); + end; + + // In any case, the sort glyph is vertically centered. + PaintInfo.SortGlyphPos.Y := Divide(ClientSize.Y - PaintInfo.SortGlyphSize.cy, 2); + end + else + begin + PaintInfo.SortGlyphSize.cx := 0; + PaintInfo.SortGlyphSize.cy := 0; + end; + end; + + if UseText then + begin + if not (coWrapCaption in FOptions) then + begin + FCaptionText := FText; + GetTextExtentPoint32W(PaintInfo.TargetCanvas.Handle, PWideChar(FText), Length(FText), TextSize); + Inc(TextSize.cx, 2); + PaintInfo.TextRectangle := Rect(0, 0, TextSize.cx, TextSize.cy); + end + else + begin + R := PaintInfo.PaintRectangle; + if FCaptionText = '' then + FCaptionText := WrapString(PaintInfo.TargetCanvas.Handle, FText, R, DT_RTLREADING and DrawFormat <> 0, DrawFormat); + + GetStringDrawRect(PaintInfo.TargetCanvas.Handle, FCaptionText, R, DrawFormat); + TextSize.cx := PaintInfo.PaintRectangle.Right - PaintInfo.PaintRectangle.Left; + TextSize.cy := R.Bottom - R.Top; + PaintInfo.TextRectangle := Rect(0, 0, TextSize.cx, TextSize.cy); + end; + TextSpacing := FSpacing; + end + else + begin + TextSpacing := 0; + TextSize.cx := 0; + TextSize.cy := 0; + end; + + // Check first for the special case where nothing is shown except the sort glyph. + if PaintInfo.ShowSortGlyph and not (UseText or PaintInfo.ShowHeaderGlyph) then + begin + // Center the sort glyph in the available area if nothing else is there. + PaintInfo.SortGlyphPos := Point(Divide(ClientSize.X - PaintInfo.SortGlyphSize.cx, 2), Divide(ClientSize.Y - PaintInfo.SortGlyphSize.cy, 2)); + end + else + begin + // Determine extents of text and glyph and calculate positions which are clear from the layout. + if (Layout in [blGlyphLeft, blGlyphRight]) or not PaintInfo.ShowHeaderGlyph then + begin + PaintInfo.GlyphPos.Y := Divide(ClientSize.Y - HeaderGlyphSize.Y, 2); + // If the text is taller than the given height, perform no vertical centration as this + // would make the text even less readable. + //Using Max() fixes badly positioned text if Extra Large fonts have been activated in the Windows display options + TextPos.Y := Max( - 5, Divide(ClientSize.Y - TextSize.cy, 2)); + end + else + begin + if Layout = blGlyphTop then + begin + PaintInfo.GlyphPos.Y := Divide(ClientSize.Y - HeaderGlyphSize.Y - TextSize.cy - TextSpacing, 2); + TextPos.Y := PaintInfo.GlyphPos.Y + HeaderGlyphSize.Y + TextSpacing; + end + else + begin + TextPos.Y := Divide(ClientSize.Y - HeaderGlyphSize.Y - TextSize.cy - TextSpacing, 2); + PaintInfo.GlyphPos.Y := TextPos.Y + TextSize.cy + TextSpacing; + end; + end; + + // Each alignment needs special consideration. + case CurrentAlignment of + taLeftJustify : + begin + MinLeft := FMargin; + if PaintInfo.ShowSortGlyph and (FBiDiMode <> bdLeftToRight) then + begin + // In RTL context is the sort glyph placed on the left hand side. + PaintInfo.SortGlyphPos.X := MinLeft; + Inc(MinLeft, PaintInfo.SortGlyphSize.cx + FSpacing); + end; + if Layout in [blGlyphTop, blGlyphBottom] then + begin + // Header glyph is above or below text, so both must be considered when calculating + // the left positition of the sort glyph (if it is on the right hand side). + TextPos.X := MinLeft; + if PaintInfo.ShowHeaderGlyph then + begin + PaintInfo.GlyphPos.X := Divide(ClientSize.X - HeaderGlyphSize.X, 2); + if PaintInfo.GlyphPos.X < MinLeft then + PaintInfo.GlyphPos.X := MinLeft; + MinLeft := Max(TextPos.X + TextSize.cx + TextSpacing, PaintInfo.GlyphPos.X + HeaderGlyphSize.X + FSpacing); + end + else + MinLeft := TextPos.X + TextSize.cx + TextSpacing; + end + else + begin + // Everything is lined up. TextSpacing might be 0 if there is no text. + // This simplifies the calculation because no extra tests are necessary. + if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphLeft) then + begin + PaintInfo.GlyphPos.X := MinLeft; + Inc(MinLeft, HeaderGlyphSize.X + FSpacing); + end; + TextPos.X := MinLeft; + Inc(MinLeft, TextSize.cx + TextSpacing); + if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphRight) then + begin + PaintInfo.GlyphPos.X := MinLeft; + Inc(MinLeft, HeaderGlyphSize.X + FSpacing); + end; + end; + if PaintInfo.ShowSortGlyph and (FBiDiMode = bdLeftToRight) then + PaintInfo.SortGlyphPos.X := MinLeft; + end; + taCenter : + begin + if Layout in [blGlyphTop, blGlyphBottom] then + begin + PaintInfo.GlyphPos.X := Divide(ClientSize.X - HeaderGlyphSize.X, 2); + TextPos.X := Divide(ClientSize.X - TextSize.cx, 2); + if PaintInfo.ShowSortGlyph then + Dec(TextPos.X, Divide(PaintInfo.SortGlyphSize.cx, 2)); + end + else + begin + MinLeft := Divide(ClientSize.X - HeaderGlyphSize.X - TextSpacing - TextSize.cx, 2); + if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphLeft) then + begin + PaintInfo.GlyphPos.X := MinLeft; + Inc(MinLeft, HeaderGlyphSize.X + TextSpacing); + end; + TextPos.X := MinLeft; + Inc(MinLeft, TextSize.cx + TextSpacing); + if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphRight) then + PaintInfo.GlyphPos.X := MinLeft; + end; + if PaintInfo.ShowHeaderGlyph then + begin + MinLeft := Min(PaintInfo.GlyphPos.X, TextPos.X); + MaxRight := Max(PaintInfo.GlyphPos.X + HeaderGlyphSize.X, TextPos.X + TextSize.cx); + end + else + begin + MinLeft := TextPos.X; + MaxRight := TextPos.X + TextSize.cx; + end; + // Place the sort glyph directly to the left or right of the larger item. + if PaintInfo.ShowSortGlyph then + if FBiDiMode = bdLeftToRight then + begin + // Sort glyph on the right hand side. + PaintInfo.SortGlyphPos.X := MaxRight + FSpacing; + end + else + begin + // Sort glyph on the left hand side. + PaintInfo.SortGlyphPos.X := MinLeft - FSpacing - PaintInfo.SortGlyphSize.cx; + end; + end; + else + // taRightJustify + MaxRight := ClientSize.X - FMargin; + if PaintInfo.ShowSortGlyph and (FBiDiMode = bdLeftToRight) then + begin + // In LTR context is the sort glyph placed on the right hand side. + Dec(MaxRight, PaintInfo.SortGlyphSize.cx); + PaintInfo.SortGlyphPos.X := MaxRight; + Dec(MaxRight, FSpacing); + end; + if Layout in [blGlyphTop, blGlyphBottom] then + begin + TextPos.X := MaxRight - TextSize.cx; + if PaintInfo.ShowHeaderGlyph then + begin + PaintInfo.GlyphPos.X := Divide(ClientSize.X - HeaderGlyphSize.X, 2); + if PaintInfo.GlyphPos.X + HeaderGlyphSize.X + FSpacing > MaxRight then + PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X - FSpacing; + MaxRight := Min(TextPos.X - TextSpacing, PaintInfo.GlyphPos.X - FSpacing); + end + else + MaxRight := TextPos.X - TextSpacing; + end + else + begin + // Everything is lined up. TextSpacing might be 0 if there is no text. + // This simplifies the calculation because no extra tests are necessary. + if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphRight) then + begin + PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X; + MaxRight := PaintInfo.GlyphPos.X - FSpacing; + end; + TextPos.X := MaxRight - TextSize.cx; + MaxRight := TextPos.X - TextSpacing; + if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphLeft) then + begin + PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X; + MaxRight := PaintInfo.GlyphPos.X - FSpacing; + end; + end; + if PaintInfo.ShowSortGlyph and (FBiDiMode <> bdLeftToRight) then + PaintInfo.SortGlyphPos.X := MaxRight - PaintInfo.SortGlyphSize.cx; + end; + end; + + // Once the position of each element is determined there remains only one but important step. + // The horizontal positions of every element must be adjusted so that it always fits into the + // given header area. This is accomplished by shorten the text appropriately. + + // These are the maximum bounds. Nothing goes beyond them. + MinLeft := FMargin; + MaxRight := ClientSize.X - FMargin; + if PaintInfo.ShowSortGlyph then + begin + if FBiDiMode = bdLeftToRight then + begin + // Sort glyph on the right hand side. + if PaintInfo.SortGlyphPos.X + PaintInfo.SortGlyphSize.cx > MaxRight then + PaintInfo.SortGlyphPos.X := MaxRight - PaintInfo.SortGlyphSize.cx; + MaxRight := PaintInfo.SortGlyphPos.X - FSpacing; + end; + + // Consider also the left side of the sort glyph regardless of the bidi mode. + if PaintInfo.SortGlyphPos.X < MinLeft then + PaintInfo.SortGlyphPos.X := MinLeft; + // Left border needs only adjustment if the sort glyph marks the left border. + if FBiDiMode <> bdLeftToRight then + MinLeft := PaintInfo.SortGlyphPos.X + PaintInfo.SortGlyphSize.cx + FSpacing; + + // Finally transform sort glyph to its actual position. + Inc(PaintInfo.SortGlyphPos.X, PaintInfo.PaintRectangle.Left); + Inc(PaintInfo.SortGlyphPos.Y, PaintInfo.PaintRectangle.Top); + end; + if PaintInfo.ShowHeaderGlyph then + begin + if PaintInfo.GlyphPos.X + HeaderGlyphSize.X > MaxRight then + PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X; + if Layout = blGlyphRight then + MaxRight := PaintInfo.GlyphPos.X - FSpacing; + if PaintInfo.GlyphPos.X < MinLeft then + PaintInfo.GlyphPos.X := MinLeft; + if Layout = blGlyphLeft then + MinLeft := PaintInfo.GlyphPos.X + HeaderGlyphSize.X + FSpacing; + if FCheckBox and (Header.MainColumn = Self.Index) then + Dec(PaintInfo.GlyphPos.X, 2) + else + if Header.MainColumn <> Self.Index then + Dec(PaintInfo.GlyphPos.X, 2); + + // Finally transform header glyph to its actual position. + Inc(PaintInfo.GlyphPos.X, PaintInfo.PaintRectangle.Left); + Inc(PaintInfo.GlyphPos.Y, PaintInfo.PaintRectangle.Top); + end; + if UseText then + begin + if TextPos.X < MinLeft then + TextPos.X := MinLeft; + OffsetRect(PaintInfo.TextRectangle, TextPos.X, TextPos.Y); + if PaintInfo.TextRectangle.Right > MaxRight then + PaintInfo.TextRectangle.Right := MaxRight; + OffsetRect(PaintInfo.TextRectangle, PaintInfo.PaintRectangle.Left, PaintInfo.PaintRectangle.Top); + + if coWrapCaption in FOptions then + begin + // Wrap the column caption if necessary. + R := PaintInfo.TextRectangle; + FCaptionText := WrapString(PaintInfo.TargetCanvas.Handle, FText, R, DT_RTLREADING and DrawFormat <> 0, DrawFormat); + GetStringDrawRect(PaintInfo.TargetCanvas.Handle, FCaptionText, R, DrawFormat); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.DefineProperties(Filer : TFiler); + +begin + inherited; + + // These properites are remains from non-Unicode Delphi versions, readers remain for backward compatibility. + Filer.DefineProperty('WideText', ReadText, nil, False); + Filer.DefineProperty('WideHint', ReadHint, nil, False); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.GetAbsoluteBounds(var Left, Right : TDimension); + +// Returns the column's left and right bounds in header coordinates, that is, independant of the scrolling position. + +begin + Left := FLeft; + Right := FLeft + FWidth; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.GetDisplayName : string; +begin + Result := FText; // Use column header caption as display name +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.GetOwner : TVirtualTreeColumns; + +begin + Result := Collection as TVirtualTreeColumns; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.InternalSetWidth(const Value : TDimension); +begin + FWidth := Value; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.ReadText(Reader : TReader); + +begin + SetText(Reader.ReadString); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.ReadHint(Reader : TReader); + +begin + FHint := Reader.ReadString; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.Assign(Source : TPersistent); + +var + OldOptions : TVTColumnOptions; + +begin + if Source is TVirtualTreeColumn then + begin + OldOptions := FOptions; + FOptions := []; + + BiDiMode := TVirtualTreeColumn(Source).BiDiMode; + ImageIndex := TVirtualTreeColumn(Source).ImageIndex; + Layout := TVirtualTreeColumn(Source).Layout; + Margin := TVirtualTreeColumn(Source).Margin; + MaxWidth := TVirtualTreeColumn(Source).MaxWidth; + MinWidth := TVirtualTreeColumn(Source).MinWidth; + Position := TVirtualTreeColumn(Source).Position; + Spacing := TVirtualTreeColumn(Source).Spacing; + Style := TVirtualTreeColumn(Source).Style; + Text := TVirtualTreeColumn(Source).Text; + Hint := TVirtualTreeColumn(Source).Hint; + Width := TVirtualTreeColumn(Source).Width; + Alignment := TVirtualTreeColumn(Source).Alignment; + CaptionAlignment := TVirtualTreeColumn(Source).CaptionAlignment; + Color := TVirtualTreeColumn(Source).Color; + Tag := TVirtualTreeColumn(Source).Tag; + EditOptions := TVirtualTreeColumn(Source).EditOptions; + EditNextColumn := TVirtualTreeColumn(Source).EditNextColumn; + + // Order is important. Assign options last. + FOptions := OldOptions; + Options := TVirtualTreeColumn(Source).Options; + + Changed(False); + end + else + inherited Assign(Source); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.Equals(OtherColumnObj : TObject) : Boolean; +var + OtherColumn : TVirtualTreeColumn; +begin + if OtherColumnObj is TVirtualTreeColumn then + begin + OtherColumn := TVirtualTreeColumn(OtherColumnObj); + Result := (BiDiMode = OtherColumn.BiDiMode) and + (ImageIndex = OtherColumn.ImageIndex) and + (Layout = OtherColumn.Layout) and + (Margin = OtherColumn.Margin) and + (MaxWidth = OtherColumn.MaxWidth) and + (MinWidth = OtherColumn.MinWidth) and + (Position = OtherColumn.Position) and + (Spacing = OtherColumn.Spacing) and + (Style = OtherColumn.Style) and + (Text = OtherColumn.Text) and + (Hint = OtherColumn.Hint) and + (Width = OtherColumn.Width) and + (Alignment = OtherColumn.Alignment) and + (CaptionAlignment = OtherColumn.CaptionAlignment) and + (Color = OtherColumn.Color) and + (Tag = OtherColumn.Tag) and + (Options = OtherColumn.Options); + end + else + Result := False; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.GetRect : TRect; + +// Returns the rectangle this column occupies in the header (relative to (0, 0) of the non-client area). + +begin + with TVirtualTreeColumns(GetOwner).FHeader do + Result := TreeViewControl.HeaderRect; + Inc(Result.Left, FLeft); + Result.Right := Result.Left + FWidth; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +// [IPK] +function TVirtualTreeColumn.GetText : string; + +begin + Result := FText; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.LoadFromStream(const Stream : TStream; Version : Integer); +var + Dummy : Integer; + S : string; + +begin + with Stream do + begin + ReadBuffer(Dummy, SizeOf(Dummy)); + SetLength(S, Dummy); + ReadBuffer(PWideChar(S)^, 2 * Dummy); + Text := S; + ReadBuffer(Dummy, SizeOf(Dummy)); + SetLength(FHint, Dummy); + ReadBuffer(PWideChar(FHint)^, 2 * Dummy); + ReadBuffer(Dummy, SizeOf(Dummy)); + Width := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + MinWidth := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + MaxWidth := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + Style := TVirtualTreeColumnStyle(Dummy); + ReadBuffer(Dummy, SizeOf(Dummy)); + ImageIndex := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + Layout := TVTHeaderColumnLayout(Dummy); + ReadBuffer(Dummy, SizeOf(Dummy)); + Margin := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + Spacing := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + BiDiMode := TBiDiMode(Dummy); + + ReadBuffer(Dummy, SizeOf(Dummy)); + if Version >= 3 then + Options := TVTColumnOptions(Dummy); + + if Version > 0 then + begin + // Parts which have been introduced/changed with header stream version 1+. + ReadBuffer(Dummy, SizeOf(Dummy)); + Tag := Dummy; + ReadBuffer(Dummy, SizeOf(Dummy)); + Alignment := TAlignment(Dummy); + + if Version > 1 then + begin + ReadBuffer(Dummy, SizeOf(Dummy)); + Color := TColor(Dummy); + end; + + if Version > 5 then + begin + if coUseCaptionAlignment in FOptions then + begin + ReadBuffer(Dummy, SizeOf(Dummy)); + CaptionAlignment := TAlignment(Dummy); + end; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.ParentBiDiModeChanged; + +var + Columns : TVirtualTreeColumns; + +begin + if coParentBidiMode in FOptions then + begin + Columns := GetOwner as TVirtualTreeColumns; + if Assigned(Columns) and (FBiDiMode <> TreeViewControl.BiDiMode) then + begin + FBiDiMode := TreeViewControl.BiDiMode; + Changed(False); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.ParentColorChanged; + +var + Columns : TVirtualTreeColumns; + +begin + if coParentColor in FOptions then + begin + Columns := GetOwner as TVirtualTreeColumns; + if Assigned(Columns) and (FColor <> TreeViewControl.Color) then + begin + FColor := TreeViewControl.Color; + Changed(False); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.RestoreLastWidth; + +begin + TVirtualTreeColumns(GetOwner).AnimatedResize(Index, FLastWidth); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumn.SaveToStream(const Stream : TStream); + +var + Dummy : Integer; + +begin + with Stream do + begin + Dummy := Length(FText); + WriteBuffer(Dummy, SizeOf(Dummy)); + WriteBuffer(PWideChar(FText)^, 2 * Dummy); + Dummy := Length(FHint); + WriteBuffer(Dummy, SizeOf(Dummy)); + WriteBuffer(PWideChar(FHint)^, 2 * Dummy); + WriteBuffer(FWidth, SizeOf(FWidth)); + WriteBuffer(FMinWidth, SizeOf(FMinWidth)); + WriteBuffer(FMaxWidth, SizeOf(FMaxWidth)); + Dummy := Ord(FStyle); + WriteBuffer(Dummy, SizeOf(Dummy)); + Dummy := FImageIndex; + WriteBuffer(Dummy, SizeOf(Dummy)); + Dummy := Ord(FLayout); + WriteBuffer(Dummy, SizeOf(Dummy)); + WriteBuffer(FMargin, SizeOf(FMargin)); + WriteBuffer(FSpacing, SizeOf(FSpacing)); + Dummy := Ord(FBiDiMode); + WriteBuffer(Dummy, SizeOf(Dummy)); + Dummy := Integer(FOptions); + WriteBuffer(Dummy, SizeOf(Dummy)); + + // parts introduced with stream version 1 + WriteBuffer(FTag, SizeOf(Dummy)); + Dummy := Cardinal(FAlignment); + WriteBuffer(Dummy, SizeOf(Dummy)); + + // parts introduced with stream version 2 + Dummy := Integer(FColor); + WriteBuffer(Dummy, SizeOf(Dummy)); + + // parts introduced with stream version 6 + if coUseCaptionAlignment in FOptions then + begin + Dummy := Cardinal(FCaptionAlignment); + WriteBuffer(Dummy, SizeOf(Dummy)); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumn.UseRightToLeftReading : Boolean; + +begin + Result := FBiDiMode <> bdLeftToRight; +end; + +//----------------- TVirtualTreeColumns -------------------------------------------------------------------------------- + +constructor TVirtualTreeColumns.Create(AOwner : TVTHeader); + +var + ColumnClass : TVirtualTreeColumnClass; + +begin + FHeader := AOwner; + + // Determine column class to be used in the header. + ColumnClass := Self.TreeViewControl.GetColumnClass; + // The owner tree always returns the default tree column class if not changed by application/descendants. + inherited Create(ColumnClass); + + FHeaderBitmap := TBitmap.Create; + FHeaderBitmap.PixelFormat := pf32Bit; + + FHoverIndex := NoColumn; + FDownIndex := NoColumn; + FClickIndex := NoColumn; + FDropTarget := NoColumn; + FTrackIndex := NoColumn; + FDefaultWidth := 50; + Self.FColumnPopupMenu := nil; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +destructor TVirtualTreeColumns.Destroy; + +begin + FreeAndNil(FColumnPopupMenu); + FreeAndNil(FHeaderBitmap); + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetCount : Integer; + +begin + Result := inherited Count; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetItem(Index : TColumnIndex) : TVirtualTreeColumn; + +begin + Result := TVirtualTreeColumn(inherited GetItem(Index)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetNewIndex(P : TPoint; var OldIndex : TColumnIndex) : Boolean; + +var + NewIndex : Integer; + +begin + Result := False; + // convert to local coordinates + Inc(P.Y, Header.Height); + NewIndex := ColumnFromPosition(P); + if NewIndex <> OldIndex then + begin + if OldIndex > NoColumn then + Header.Invalidate(Items[OldIndex], False, True); + OldIndex := NewIndex; + if OldIndex > NoColumn then + Header.Invalidate(Items[OldIndex], False, True); + Result := True; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.SetDefaultWidth(Value : TDimension); + +begin + FDefaultWidth := Value; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.SetItem(Index : TColumnIndex; Value : TVirtualTreeColumn); + +begin + inherited SetItem(Index, Value); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.StyleServices(AControl : TControl) : TCustomStyleServices; +begin + if AControl = nil then + AControl := TreeView; + Result := VTStyleServices(AControl); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.AdjustAutoSize(CurrentIndex : TColumnIndex; Force : Boolean = False); + +// Called only if the header is in auto-size mode which means a column needs to be so large +// that it fills all the horizontal space not occupied by the other columns. +// CurrentIndex (if not InvalidColumn) describes which column has just been resized. + +var + AutoIndex, + Index: Integer; + NewValue, RestWidth : TDimension; + WasUpdating : Boolean; +begin + if Count > 0 then + begin + // Determine index to be used for auto resizing. This is usually given by the owner's AutoSizeIndex, but + // could be different if the column whose resize caused the invokation here is either the auto column itself + // or visually to the right of the auto size column. + AutoIndex := Header.AutoSizeIndex; + if (AutoIndex < 0) or (AutoIndex >= Count) then + AutoIndex := Count - 1; + + if AutoIndex >= 0 then + begin + with TreeViewControl do + begin + if HandleAllocated then + RestWidth := ClientWidth + else + RestWidth := Width; + end; + + // Go through all columns and calculate the rest space remaining. + for Index := 0 to Count - 1 do + if (Index <> AutoIndex) and (coVisible in Items[Index].Options) then + Dec(RestWidth, Items[Index].Width); + + with Items[AutoIndex] do + begin + NewValue := Max(MinWidth, Min(MaxWidth, RestWidth)); + if Force or (FWidth <> NewValue) then + begin + FWidth := NewValue; + UpdatePositions; + WasUpdating := csUpdating in TreeViewControl.ComponentState; + if not WasUpdating then + TreeViewControl.Updating(); // Fixes #398 + try + TreeViewControl.DoColumnResize(AutoIndex); + finally + if not WasUpdating then + TreeViewControl.Updated(); + end; + end; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.AdjustDownColumn(P : TPoint) : TColumnIndex; + +// Determines the column from the given position and returns it. If this column is allowed to be clicked then +// it is also kept for later use. + +begin + // Convert to local coordinates. + Inc(P.Y, Header.Height); + Result := ColumnFromPosition(P); + if (Result > NoColumn) and (Result <> FDownIndex) and (coAllowClick in Items[Result].Options) and + (coEnabled in Items[Result].Options) then + begin + if FDownIndex > NoColumn then + Header.Invalidate(Items[FDownIndex]); + FDownIndex := Result; + FCheckBoxHit := Items[Result].HasImage and PtInRect(Items[Result].ImageRect, P) and Items[Result].CheckBox; + Header.Invalidate(Items[FDownIndex]); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.AdjustHoverColumn(P : TPoint) : Boolean; + +// Determines the new hover column index and returns True if the index actually changed else False. + +begin + Result := GetNewIndex(P, FHoverIndex); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.AdjustPosition(Column : TVirtualTreeColumn; Position : Cardinal); + +// Reorders the column position array so that the given column gets the given position. + +var + OldPosition : Cardinal; + +begin + OldPosition := Column.Position; + if OldPosition <> Position then + begin + if OldPosition < Position then + begin + // column will be moved up so move down other entries + Move(FPositionToIndex[OldPosition + 1], FPositionToIndex[OldPosition], (Position - OldPosition) * SizeOf(Cardinal)); + end + else + begin + // column will be moved down so move up other entries + Move(FPositionToIndex[Position], FPositionToIndex[Position + 1], (OldPosition - Position) * SizeOf(Cardinal)); + end; + FPositionToIndex[Position] := Column.Index; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.CanSplitterResize(P : TPoint; Column : TColumnIndex) : Boolean; + +begin + Result := (Column > NoColumn) and ([coResizable, coVisible] * Items[Column].Options = [coResizable, coVisible]); + DoCanSplitterResize(P, Column, Result); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.DoCanSplitterResize(P : TPoint; Column : TColumnIndex; var Allowed : Boolean); + +begin + if Assigned(TreeViewControl.OnCanSplitterResizeColumn) then + TreeViewControl.OnCanSplitterResizeColumn(Header, P, Column, Allowed); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.DrawButtonText(DC : HDC; Caption : string; Bounds : TRect; Enabled, Hot : Boolean; + DrawFormat : Cardinal; WrapCaption : Boolean); + +var + TextSpace : TDimension; + Size : TSize; + +begin + if not WrapCaption then + begin + // Do we need to shorten the caption due to limited space? + GetTextExtentPoint32W(DC, PWideChar(Caption), Length(Caption), Size); + TextSpace := Bounds.Right - Bounds.Left; + if TextSpace < Size.cx then + Caption := ShortenString(DC, Caption, TextSpace); + end; + + SetBkMode(DC, TRANSPARENT); + if not Enabled then + if TreeViewControl.VclStyleEnabled then + begin + SetTextColor(DC, ColorToRGB(TreeViewControl.Colors.HeaderFontColor)); + WinApi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat); + end + else + begin + OffsetRect(Bounds, 1, 1); + SetTextColor(DC, ColorToRGB(clBtnHighlight)); + WinApi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat); + OffsetRect(Bounds, - 1, - 1); + SetTextColor(DC, ColorToRGB(clBtnShadow)); + WinApi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat); + end + else + begin + if Hot then + SetTextColor(DC, ColorToRGB(TreeViewControl.Colors.HeaderHotColor)) + else + SetTextColor(DC, ColorToRGB(TreeViewControl.Colors.HeaderFontColor)); + WinApi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.FixPositions; +// Fixes column positions after loading from DFM or Bidi mode change. +var + LColumnsByPos: TList; + I: Integer; +begin + LColumnsByPos := TList.Create; + try + LColumnsByPos.Capacity := Self.Count; + for I := 0 to Self.Count-1 do + LColumnsByPos.Add(Items[I]); + + LColumnsByPos.Sort( + TComparer.Construct( + function(const A, B: TVirtualTreeColumn): Integer + begin + Result := CompareValue(A.Position, B.Position); + if Result = 0 then + Result := CompareValue(A.Index, B.Index); + end) + ); + + for I := 0 to LColumnsByPos.Count-1 do + begin + LColumnsByPos[I].FPosition := I; + Self.FPositionToIndex[I] := LColumnsByPos[I].Index; + end; + + finally + LColumnsByPos.Free; + end; + + FNeedPositionsFix := False; + UpdatePositions(True); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetColumnAndBounds(P : TPoint; var ColumnLeft, ColumnRight : TDimension; + Relative : Boolean = True) : Integer; + +// Returns the column where the mouse is currently in as well as the left and right bound of +// this column (Left and Right are undetermined if no column is involved). + +var + I : Integer; + +begin + Result := InvalidColumn; + if Relative and (P.X >= Header.Columns.GetVisibleFixedWidth) then + ColumnLeft := - TreeViewControl.EffectiveOffsetX + else + ColumnLeft := 0; + + if TreeViewControl.UseRightToLeftAlignment then + Inc(ColumnLeft, TreeViewControl.ComputeRTLOffset(True)); + + for I := 0 to Count - 1 do + with Items[FPositionToIndex[I]] do + if coVisible in FOptions then + begin + ColumnRight := ColumnLeft + FWidth; + + //fix: in right to left alignment, X can be in the + //area on the left of first column which is OUT. + if (P.X < ColumnLeft) and (I = 0) then + begin + Result := InvalidColumn; + Exit; + end; + if P.X < ColumnRight then + begin + Result := FPositionToIndex[I]; + Exit; + end; + ColumnLeft := ColumnRight; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetOwner : TPersistent; + +begin + Result := FHeader; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.HandleClick(P : TPoint; Button : TMouseButton; Force, DblClick : Boolean) : Boolean; + +// Generates a click event if the mouse button has been released over the same column it was pressed first. +// Alternatively, Force might be set to True to indicate that the down index does not matter (right, middle and +// double click). +// Returns true if the click was handled, False otherwise. + +var + HitInfo : TVTHeaderHitInfo; + NewClickIndex : Integer; + Menu : TPopupMenu; +begin + Result := False; + if (csDesigning in TreeViewControl.ComponentState) then + Exit; + // Convert vertical position to local coordinates. + Inc(P.Y, Header.Height); + NewClickIndex := ColumnFromPosition(P); + with HitInfo do + begin + X := P.X; + Y := P.Y; + Shift := Header.GetShiftState; + if DblClick then + Shift := Shift + [ssDouble]; + end; + HitInfo.Button := Button; + + if (NewClickIndex > NoColumn) and (coAllowClick in Items[NewClickIndex].Options) and + ((NewClickIndex = FDownIndex) or Force) then + begin + FClickIndex := NewClickIndex; + HitInfo.Column := NewClickIndex; + HitInfo.HitPosition := [hhiOnColumn]; + + if Items[NewClickIndex].HasImage and PtInRect(Items[NewClickIndex].ImageRect, P) then + begin + Include(HitInfo.HitPosition, hhiOnIcon); + if Items[NewClickIndex].CheckBox then + begin + if Button = TMouseButton.mbLeft then + TreeViewControl.UpdateColumnCheckState(Items[NewClickIndex]); + Include(HitInfo.HitPosition, hhiOnCheckbox); + end; + end; + end + else + begin + FClickIndex := NoColumn; + HitInfo.Column := NoColumn; + HitInfo.HitPosition := [hhiNoWhere]; + end; + + if DblClick then + TreeViewControl.DoHeaderDblClick(HitInfo) + else begin + if (hoHeaderClickAutoSort in Header.Options) and (HitInfo.Button = TMouseButton.mbLeft) and not (hhiOnCheckbox in HitInfo.HitPosition) and (HitInfo.Column >= 0) then + begin + // handle automatic setting of SortColumn and toggling of the sort order + if HitInfo.Column <> Header.SortColumn then + begin + // set sort column + Header.DoSetSortColumn(HitInfo.Column, Self[HitInfo.Column].DefaultSortDirection); + end//if + else + begin + // toggle sort direction + if Header.SortDirection = sdDescending then + Header.SortDirection := sdAscending + else + Header.SortDirection := sdDescending; + end; //else + Result := True; + end; //if + + if (Button = TMouseButton.mbRight) then + begin + Dec(P.Y, Header.Height); // popup menus at actual clicked point + FreeAndNil(FColumnPopupMenu); // Attention: Do not free the TVTHeaderPopupMenu at the end of this method, otherwise the clikc events of the menu item will not be fired. + Self.FDownIndex := NoColumn; + Self.FTrackIndex := NoColumn; + Self.FCheckBoxHit := False; + Menu := Header.DoGetPopupMenu(Self.ColumnFromPosition(Point(P.X, P.Y + TreeViewControl.Height)), P); + if Assigned(Menu) then + begin + TreeViewControl.StopTimer(ScrollTimer); + TreeViewControl.StopTimer(HeaderTimer); + Header.Columns.SetHoverIndex(NoColumn); + TreeViewControl.DoStateChange([], [tsScrollPending, tsScrolling]); + + Menu.PopupComponent := TreeViewControl; + With TreeViewControl.ClientToScreen(P) do + Menu.Popup(X, Y); + Result := True; + end + else if (hoAutoColumnPopupMenu in Header.Options) then + begin + FColumnPopupMenu := TVTHeaderPopupMenu.Create(TreeViewControl); + TVTHeaderPopupMenu(FColumnPopupMenu).OnAddHeaderPopupItem := HeaderPopupMenuAddHeaderPopupItem; + FColumnPopupMenu.PopupComponent := TreeViewControl; + if (hoDblClickResize in Header.Options) and ((TreeViewControl.ChildCount[nil] > 0) or (hoAutoResizeInclCaption in Header.Options)) then + TVTHeaderPopupMenu(FColumnPopupMenu).Options := TVTHeaderPopupMenu(FColumnPopupMenu).Options + [poResizeToFitItem] + else + TVTHeaderPopupMenu(FColumnPopupMenu).Options := TVTHeaderPopupMenu(FColumnPopupMenu).Options - [poResizeToFitItem]; + With TreeViewControl.ClientToScreen(P) do + FColumnPopupMenu.Popup(X, Y); + Result := True; + end; // if hoAutoColumnPopupMenu + end; //if mbRight + TreeViewControl.DoHeaderClick(HitInfo); + end; //else (not DblClick) + + if not (hhiNoWhere in HitInfo.HitPosition) then + Header.Invalidate(Items[NewClickIndex]); + if (FClickIndex > NoColumn) and (FClickIndex <> NewClickIndex) then + Header.Invalidate(Items[FClickIndex]); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.HeaderPopupMenuAddHeaderPopupItem(const Sender : TObject; const Column : TColumnIndex; var Cmd : TAddPopupItemType); +begin + TBaseVirtualTreeCracker(Sender).DoHeaderAddPopupItem(Column, Cmd); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.IndexChanged(OldIndex, NewIndex : Integer); + +// Called by a column when its index in the collection changes. If NewIndex is -1 then the column is +// about to be removed, otherwise it is moved to a new index. +// The method will then update the position array to reflect the change. + +var + I : Integer; + Increment : Integer; + Lower, + Upper : Integer; + +begin + if NewIndex = - 1 then + begin + // Find position in the array with the old index. + Upper := High(FPositionToIndex); + for I := 0 to Upper do + begin + if FPositionToIndex[I] = OldIndex then + begin + // Index found. Move all higher entries one step down and remove the last entry. + if I < Upper then + System.Move(FPositionToIndex[I + 1], FPositionToIndex[I], (Upper - I) * SizeOf(TColumnIndex)); + end; + // Decrease all indices, which are greater than the index to be deleted. + if FPositionToIndex[I] > OldIndex then + System.Dec(FPositionToIndex[I]); + end; + SetLength(FPositionToIndex, High(FPositionToIndex)); + end + else + begin + if OldIndex < NewIndex then + Increment := - 1 + else + Increment := 1; + + Lower := Min(OldIndex, NewIndex); + Upper := Max(OldIndex, NewIndex); + for I := 0 to High(FPositionToIndex) do + begin + if (FPositionToIndex[I] >= Lower) and (FPositionToIndex[I] < Upper) then + System.Inc(FPositionToIndex[I], Increment) + else + if FPositionToIndex[I] = OldIndex then + FPositionToIndex[I] := NewIndex; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.InitializePositionArray; + +// Ensures that the column position array contains as many entries as columns are defined. +// The array is resized and initialized with default values if needed. + +var + I, OldSize : Integer; + Changed : Boolean; + +begin + if Count <> Length(FPositionToIndex) then + begin + OldSize := Length(FPositionToIndex); + SetLength(FPositionToIndex, Count); + if Count > OldSize then + begin + // New items have been added, just set their position to the same as their index. + for I := OldSize to Count - 1 do + FPositionToIndex[I] := I; + end + else + begin + // Items have been deleted, so reindex remaining entries by decrementing values larger than the highest + // possible index until no entry is higher than this limit. + repeat + Changed := False; + for I := 0 to Count - 1 do + if FPositionToIndex[I] >= Count then + begin + System.Dec(FPositionToIndex[I]); + Changed := True; + end; + until not Changed; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.Notify(Item : TCollectionItem; Action : System.Classes.TCollectionNotification); +var + I : Integer; + lRemovedPosition: TColumnPosition; +begin + if Action in [cnDeleting] then + begin + lRemovedPosition := TVirtualTreeColumn(Item).Position; + // Adjust all positions larger than the deleted column's position. Fixes #959, #1049 + for I := Count - 1 downto 0 do + begin + if Items[I].Position > lRemovedPosition then + Items[I].Position := Items[I].Position - 1; + end; //for I + + with TreeViewControl do + if not (csLoading in ComponentState) and (FocusedColumn = Item.Index) then + InternalSetFocusedColumn(NoColumn); //bypass side effects in SetFocusedColumn + end; // if cnDeleting +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.ReorderColumns(RTL : Boolean); + +var + I : Integer; + +begin + if RTL then + begin + for I := 0 to Count - 1 do + FPositionToIndex[I] := Count - I - 1; + end + else + begin + for I := 0 to Count - 1 do + FPositionToIndex[I] := I; + end; + + UpdatePositions(True); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.SetHoverIndex(Index : TColumnIndex); +begin + FHoverIndex := index; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.EndUpdate; +begin + InitializePositionArray(); + FixPositions(); // Accept the cuurent order. See issue #753 + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.Update(Item : TCollectionItem); + +begin + // This is the only place which gets notified when a new column has been added or removed + // and we need this event to adjust the column position array. + InitializePositionArray; + if csLoading in TreeViewControl.ComponentState then + FNeedPositionsFix := True + else + UpdatePositions; + + // The first column which is created is by definition also the main column. + if (Count > 0) and (Header.MainColumn < 0) then + Header.MainColumn := 0; + + if not (csLoading in TreeViewControl.ComponentState) and not (hsLoading in Header.States) then + begin + with Header do + begin + if hoAutoResize in Options then + AdjustAutoSize(InvalidColumn); + if Assigned(Item) then + Invalidate(Item as TVirtualTreeColumn) + else + if Self.TreeViewControl.HandleAllocated then + begin + Self.TreeViewControl.UpdateHorizontalScrollBar(False); + Invalidate(nil); + TreeViewControl.Invalidate; + end; + + if not (Self.TreeViewControl.IsUpdating) then + // This is mainly to let the designer know when a change occurs at design time which + // doesn't involve the object inspector (like column resizing with the mouse). + // This does NOT include design time code as the communication is done via an interface. + Self.TreeViewControl.UpdateDesigner; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.UpdatePositions(Force : Boolean = False); + +// Recalculates the left border of every column and updates their position property according to the +// PostionToIndex array which primarily determines where each column is placed visually. + +var + I: Integer; + RunningPos: TDimension; +begin + if not (csDestroying in TreeViewControl.ComponentState) and not FNeedPositionsFix and (Force or (UpdateCount = 0)) then + begin + RunningPos := 0; + for I := 0 to High(FPositionToIndex) do + with Items[FPositionToIndex[I]] do + begin + FPosition := I; + FLeft := RunningPos; + if coVisible in FOptions then + Inc(RunningPos, FWidth); + end; + TreeViewControl.UpdateHorizontalScrollBar(False); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.Add : TVirtualTreeColumn; + +begin + Assert(GetCurrentThreadId = MainThreadId, 'UI controls may only be changed in UI thread.'); + Result := TVirtualTreeColumn(inherited Add); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.AnimatedResize(Column : TColumnIndex; NewWidth : TDimension); + +// Resizes the given column animated by scrolling the window DC. + +var + OldWidth : TDimension; + DC : TCanvas; + I, Steps : Integer; + DX : TDimension; + HeaderScrollRect, + ScrollRect, + R : TRect; + +begin + if not IsValidColumn(Column) then + Exit; // Just in case. + + // Make sure the width constrains are considered. + if NewWidth < Items[Column].MinWidth then + NewWidth := Items[Column].MinWidth; + if NewWidth > Items[Column].MaxWidth then + NewWidth := Items[Column].MaxWidth; + + OldWidth := Items[Column].Width; + // Nothing to do if the width is the same. + if OldWidth <> NewWidth then + begin + if not ((hoDisableAnimatedResize in Header.Options) or + (coDisableAnimatedResize in Items[Column].Options)) then + begin + DC := TCanvas.Create; + DC.Handle := GetWindowDC(TreeViewControl.Handle); + with TreeViewControl do + try + Steps := 32; + DX := Divide(NewWidth - OldWidth, Steps); + + // Determination of the scroll rectangle is a bit complicated since we neither want + // to scroll the scrollbars nor the border of the treeview window. + HeaderScrollRect := HeaderRect; + ScrollRect := HeaderScrollRect; + // Exclude the header itself from scrolling. + ScrollRect.Top := ScrollRect.Bottom; + ScrollRect.Bottom := ScrollRect.Top + ClientHeight; + ScrollRect.Right := ScrollRect.Left + ClientWidth; + with Items[Column] do + Inc(ScrollRect.Left, FLeft + FWidth); + HeaderScrollRect.Left := ScrollRect.Left; + HeaderScrollRect.Right := ScrollRect.Right; + + // When the new width is larger then avoid artefacts on the left hand side + // by deleting a small stripe + if NewWidth > OldWidth then + begin + R := ScrollRect; +// NewBrush := CreateSolidBrush(ColorToRGB(Color)); +// LastBrush := SelectObject(DC, NewBrush); + R.Right := R.Left + DX; +// FillRect(DC, R, NewBrush); +// SelectObject(DC, LastBrush); +// DeleteObject(NewBrush); + DC.Brush.Color := Color; + DC.FillRect(R); + end + else + begin + Inc(HeaderScrollRect.Left, DX); + Inc(ScrollRect.Left, DX); + end; + + for I := 0 to Steps - 1 do + begin + ScrollDC(DC.Handle, DX, 0, HeaderScrollRect, HeaderScrollRect, 0, nil); + Inc(HeaderScrollRect.Left, DX); + ScrollDC(DC.Handle, DX, 0, ScrollRect, ScrollRect, 0, nil); + Inc(ScrollRect.Left, DX); + Sleep(1); + end; + finally + ReleaseDC(Handle, DC.Handle); + DC.Free; + end; + end; + Items[Column].Width := NewWidth; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.Assign(Source : TPersistent); + +begin + // Let the collection class assign the items. + inherited; + + if Source is TVirtualTreeColumns then + begin + // Copying the position array is the only needed task here. + FPositionToIndex := Copy(TVirtualTreeColumns(Source).FPositionToIndex, 0, MaxInt); + + // Make sure the left edges are correct after assignment. + FNeedPositionsFix := False; + UpdatePositions(True); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.Clear; + +begin + FClearing := True; + try + TreeViewControl.CancelEditNode; + + // Since we're freeing all columns, the following have to be true when we're done. + FHoverIndex := NoColumn; + FDownIndex := NoColumn; + FTrackIndex := NoColumn; + FClickIndex := NoColumn; + FCheckBoxHit := False; + + with Header do + if not (hsLoading in States) then + begin + InternalSetAutoSizeIndex(NoColumn); //bypass side effects in SetAutoSizeColumn + MainColumn := NoColumn; + InternalSetSortColumn(NoColumn); //bypass side effects in SetSortColumn + end; + + with TreeViewControl do + if not (csLoading in ComponentState) then + InternalSetFocusedColumn(NoColumn); //bypass side effects in SetFocusedColumn + + inherited Clear; + finally + FClearing := False; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.ColumnFromPosition(P : TPoint; Relative : Boolean = True) : TColumnIndex; + +// Determines the current column based on the position passed in P. + +var + I: Integer; + Sum: TDimension; +begin + Result := InvalidColumn; + + // The position must be within the header area, but we extend the vertical bounds to the entire treeview area. + if (P.X >= 0) and (P.Y >= 0) and (P.Y <= TreeViewControl.Height) then + with FHeader, TreeViewControl do + begin + if Relative and (P.X >= GetVisibleFixedWidth) then + Sum := - EffectiveOffsetX + else + Sum := 0; + + if UseRightToLeftAlignment then + Inc(Sum, ComputeRTLOffset(True)); + + for I := 0 to Count - 1 do + if coVisible in Items[FPositionToIndex[I]].Options then + begin + Inc(Sum, Items[FPositionToIndex[I]].Width); + if P.X < Sum then + begin + Result := FPositionToIndex[I]; + Break; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.ColumnFromPosition(PositionIndex : TColumnPosition) : TColumnIndex; + +// Returns the index of the column at the given position. + +begin + if Integer(PositionIndex) < Length(FPositionToIndex) then + Result := FPositionToIndex[PositionIndex] + else + Result := NoColumn; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.Equals(OtherColumnsObj : TObject) : Boolean; + +// Compares itself with the given set of columns and returns True if all published properties are the same +// (including column order), otherwise False is returned. + +var + I : Integer; + OtherColumns : TVirtualTreeColumns; + +begin + if not (OtherColumnsObj is TVirtualTreeColumns) then + begin + Result := False; + Exit; + end; + + OtherColumns := TVirtualTreeColumns(OtherColumnsObj); + + // Same number of columns? + Result := OtherColumns.Count = Count; + if Result then + begin + // Same order of columns? + Result := CompareMem(Pointer(FPositionToIndex), Pointer(OtherColumns.FPositionToIndex), + Length(FPositionToIndex) * SizeOf(TColumnIndex)); + if Result then + begin + for I := 0 to Count - 1 do + if not Items[I].Equals(OtherColumns[I]) then + begin + Result := False; + Break; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.GetColumnBounds(Column : TColumnIndex; var Left, Right : TDimension); + +// Returns the left and right bound of the given column. If Column is NoColumn then the entire client width is returned. + +begin + if Column <= NoColumn then + begin + Left := 0; + Right := TreeViewControl.ClientWidth; + end + else + begin + Left := Items[Column].Left; + Right := Left + Items[Column].Width; + if TreeViewControl.UseRightToLeftAlignment then + begin + Inc(Left, TreeViewControl.ComputeRTLOffset(True)); + Inc(Right, TreeViewControl.ComputeRTLOffset(True)); + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetScrollWidth : TDimension; + +// Returns the average width of all visible, non-fixed columns. If there is no such column the indent is returned. + +var + I : Integer; + ScrollColumnCount : Integer; + +begin + + Result := 0; + + ScrollColumnCount := 0; + for I := 0 to Header.Columns.Count - 1 do + begin + if ([coVisible, coFixed] * Header.Columns[I].Options = [coVisible]) then + begin + Inc(Result, Header.Columns[I].Width); + System.Inc(ScrollColumnCount); + end; + end; + + if ScrollColumnCount > 0 then // use average width + Result := Round(Result / ScrollColumnCount) + else // use indent + Result := TreeViewControl.Indent; + +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetTreeView: TCustomControl; +begin + Result := TBaseVirtualTreeCracker(Header.GetOwner); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetFirstVisibleColumn(ConsiderAllowFocus : Boolean = False) : TColumnIndex; + +// Returns the index of the first visible column or "InvalidColumn" if either no columns are defined or +// all columns are hidden. +// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed. + +var + I : Integer; + +begin + Result := InvalidColumn; + if (UpdateCount > 0) or (csLoading in TreeViewControl.ComponentState) then + Exit; // See issue #760 + for I := 0 to Count - 1 do + if (coVisible in Items[FPositionToIndex[I]].Options) and + ((not ConsiderAllowFocus) or + (coAllowFocus in Items[FPositionToIndex[I]].Options) + ) then + begin + Result := FPositionToIndex[I]; + Break; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetLastVisibleColumn(ConsiderAllowFocus : Boolean = False) : TColumnIndex; + +// Returns the index of the last visible column or "InvalidColumn" if either no columns are defined or +// all columns are hidden. +// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed. + +var + I : Integer; + +begin + Result := InvalidColumn; + if (UpdateCount > 0) or (csLoading in TreeViewControl.ComponentState) then + Exit; // See issue #760 + for I := Count - 1 downto 0 do + if (coVisible in Items[FPositionToIndex[I]].Options) and + ((not ConsiderAllowFocus) or + (coAllowFocus in Items[FPositionToIndex[I]].Options) + ) then + begin + Result := FPositionToIndex[I]; + Break; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetFirstColumn : TColumnIndex; + +// Returns the first column in display order. + +begin + if Count = 0 then + Result := InvalidColumn + else + Result := FPositionToIndex[0]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetNextColumn(Column : TColumnIndex) : TColumnIndex; + +// Returns the next column in display order. Column is the index of an item in the collection (a column). + +var + Position : Integer; + +begin + if Column < 0 then + Result := InvalidColumn + else + begin + Position := Items[Column].Position; + if Position < Count - 1 then + Result := FPositionToIndex[Position + 1] + else + Result := InvalidColumn; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetNextVisibleColumn(Column : TColumnIndex; ConsiderAllowFocus : Boolean = False) : TColumnIndex; + +// Returns the next visible column in display order, Column is an index into the columns list. +// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed. + +begin + Result := Column; + repeat + Result := GetNextColumn(Result); + until (Result = InvalidColumn) or + ((coVisible in Items[Result].Options) and + ((not ConsiderAllowFocus) or + (coAllowFocus in Items[Result].Options) + ) + ); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetPreviousColumn(Column : TColumnIndex) : TColumnIndex; + +// Returns the previous column in display order, Column is an index into the columns list. + +var + Position : Integer; + +begin + if Column < 0 then + Result := InvalidColumn + else + begin + Position := Items[Column].Position; + if Position > 0 then + Result := FPositionToIndex[Position - 1] + else + Result := InvalidColumn; + Assert(Column <> Result, 'The previous column must not have the same position as the given column.'); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetPreviousVisibleColumn(Column : TColumnIndex; ConsiderAllowFocus : Boolean = False) : TColumnIndex; + +// Returns the previous visible column in display order, Column is an index into the columns list. +// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed. + +begin + Result := Column; + repeat + Result := GetPreviousColumn(Result); + until (Result = InvalidColumn) or + ((coVisible in Items[Result].Options) and + ((not ConsiderAllowFocus) or + (coAllowFocus in Items[Result].Options) + ) + ); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetVisibleColumns : TColumnsArray; + +// Returns a list of all currently visible columns in actual order. + +var + I, Counter : Integer; + +begin + SetLength(Result, Count); + Counter := 0; + + for I := 0 to Count - 1 do + if coVisible in Items[FPositionToIndex[I]].Options then + begin + Result[Counter] := Items[FPositionToIndex[I]]; + System.Inc(Counter); + end; + // Set result length to actual visible count. + SetLength(Result, Counter); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.GetVisibleFixedWidth : TDimension; + +// Determines the horizontal space all visible and fixed columns occupy. + +var + I : Integer; + +begin + Result := 0; + for I := 0 to Count - 1 do + begin + if Items[I].Options * [coVisible, coFixed] = [coVisible, coFixed] then + Inc(Result, Items[I].Width); + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.IsValidColumn(Column : TColumnIndex) : Boolean; + +// Determines whether the given column is valid or not, that is, whether it is one of the current columns. + +begin + Result := (Column > NoColumn) and (Column < Count); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.LoadFromStream(const Stream : TStream; Version : Integer); + +var + I, + ItemCount : Integer; + +begin + Clear; + Stream.ReadBuffer(ItemCount, SizeOf(ItemCount)); + // number of columns + if ItemCount > 0 then + begin + BeginUpdate; + try + for I := 0 to ItemCount - 1 do + Add.LoadFromStream(Stream, Version); + SetLength(FPositionToIndex, ItemCount); + Stream.ReadBuffer(FPositionToIndex[0], ItemCount * SizeOf(TColumnIndex)); + UpdatePositions(True); + finally + EndUpdate; + end; + end; + + // Data introduced with header stream version 5 + if Version > 4 then + Stream.ReadBuffer(FDefaultWidth, SizeOf(FDefaultWidth)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.PaintHeader(DC : HDC; R : TRect; HOffset : TDimension); + +// Backward compatible header paint method. This method takes care of visually moving floating columns + +var + VisibleFixedWidth : TDimension; + RTLOffset : TDimension; + + procedure PaintFixedArea; + + begin + if VisibleFixedWidth > 0 then + PaintHeader(FHeaderBitmap.Canvas, + Rect(0, 0, Min(R.Right, VisibleFixedWidth), R.Bottom - R.Top), + Point(R.Left, R.Top), RTLOffset); + end; + +begin + // Adjust size of the header bitmap + FHeaderBitmap.SetSize(Max(TreeViewControl.HeaderRect.Right, R.Right - R.Left), TreeViewControl.HeaderRect.Bottom); + + VisibleFixedWidth := GetVisibleFixedWidth; + + // Consider right-to-left directionality. + if TreeViewControl.UseRightToLeftAlignment then + RTLOffset := TreeViewControl.ComputeRTLOffset + else + RTLOffset := 0; + + if RTLOffset = 0 then + PaintFixedArea; + + // Paint the floating part of the header. + PaintHeader(FHeaderBitmap.Canvas, + Rect(VisibleFixedWidth - HOffset, 0, R.Right + VisibleFixedWidth - HOffset, R.Bottom - R.Top), + Point(R.Left + VisibleFixedWidth, R.Top), RTLOffset); + + // In case of right-to-left directionality we paint the fixed part last. + if RTLOffset <> 0 then + PaintFixedArea; + + // Blit the result to target. + BitBlt(DC, R.Left, R.Top, R.Right - R.Left, R.Bottom - R.Top, FHeaderBitmap.Canvas.Handle, R.Left, R.Top, SRCCOPY); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.PaintHeader(TargetCanvas : TCanvas; R : TRect; const Target : TPoint; + RTLOffset : TDimension = 0); + +// Main paint method to draw the header. +// This procedure will paint the a slice (given in R) out of HeaderRect into TargetCanvas starting at position Target. +// This function does not offer the option to visually move floating columns due to scrolling. To accomplish this you +// need to call this method twice. + +var + Run : TColumnIndex; + RightBorderFlag, + NormalButtonStyle, + NormalButtonFlags, + PressedButtonStyle, + PressedButtonFlags, + RaisedButtonStyle, + RaisedButtonFlags : Cardinal; + Images : TCustomImageList; + OwnerDraw, + AdvancedOwnerDraw : Boolean; + PaintInfo : THeaderPaintInfo; + RequestedElements, + ActualElements : THeaderPaintElements; + + //--------------- local functions ------------------------------------------- + + procedure PrepareButtonStyles; + + // Prepare the button styles and flags for later usage. + + begin + RaisedButtonStyle := 0; + RaisedButtonFlags := 0; + case Header.Style of + hsThickButtons : + begin + NormalButtonStyle := BDR_RAISEDINNER or BDR_RAISEDOUTER; + NormalButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_SOFT or BF_ADJUST; + PressedButtonStyle := BDR_RAISEDINNER or BDR_RAISEDOUTER; + PressedButtonFlags := NormalButtonFlags or BF_RIGHT or BF_FLAT or BF_ADJUST; + end; + hsFlatButtons : + begin + NormalButtonStyle := BDR_RAISEDINNER; + NormalButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_ADJUST; + PressedButtonStyle := BDR_SUNKENOUTER; + PressedButtonFlags := BF_RECT or BF_MIDDLE or BF_ADJUST; + end; + else + // hsPlates or hsXPStyle, values are not used in the latter case + begin + NormalButtonStyle := BDR_RAISEDINNER; + NormalButtonFlags := BF_RECT or BF_MIDDLE or BF_SOFT or BF_ADJUST; + PressedButtonStyle := BDR_SUNKENOUTER; + PressedButtonFlags := BF_RECT or BF_MIDDLE or BF_ADJUST; + RaisedButtonStyle := BDR_RAISEDINNER; + RaisedButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_ADJUST; + end; + end; + end; + + //--------------------------------------------------------------------------- + + procedure DrawBackground; + + // Draw the header background. + + var + BackgroundRect : TRect; + Details : TThemedElementDetails; + Theme : HTHEME; + begin + BackgroundRect := Rect(Target.X, Target.Y, Target.X + R.Right - R.Left, Target.Y + Header.Height); + + with TargetCanvas do + begin + if hpeBackground in RequestedElements then + begin + PaintInfo.PaintRectangle := BackgroundRect; + TreeViewControl.DoAdvancedHeaderDraw(PaintInfo, [hpeBackground]); + end + else + begin + if (TreeViewControl.VclStyleEnabled and (seClient in TreeViewControl.StyleElements)) then + begin + Details := StyleServices.GetElementDetails(thHeaderItemRightNormal); + StyleServices.DrawElement(Handle, Details, BackgroundRect, @BackgroundRect {$IF CompilerVersion >= 34}, TreeViewControl.FCurrentPPI{$IFEND}); + end + else + if tsUseThemes in TreeViewControl.TreeStates then + begin + Theme := OpenThemeData(TreeViewControl.Handle, 'HEADER'); + DrawThemeBackground(Theme, Handle, HP_HEADERITEM, HIS_NORMAL, BackgroundRect, nil); + CloseThemeData(Theme); + end + else + begin + Brush.Color := Header.Background; + FillRect(BackgroundRect); + end; + end; + end; + end; + + //--------------------------------------------------------------------------- + + procedure PaintColumnHeader(AColumn : TColumnIndex; ATargetRect : TRect); + + // Draw a single column to TargetRect. The clipping rect needs to be set before + // this procedure is called. + + var + SavedDC : Integer; + ColCaptionText : string; + ColImageInfo : TVTImageInfo; + Glyph : TThemedHeader; + Details : TThemedElementDetails; + WrapCaption : Boolean; + DrawFormat : Cardinal; + Pos : TRect; + DrawHot : Boolean; + ImageWidth : Integer; + begin + ColImageInfo.Ghosted := False; + PaintInfo.Column := Items[AColumn]; + with PaintInfo, Column do + begin + IsHoverIndex := (AColumn = FHoverIndex) and (hoHotTrack in Header.Options) and (coEnabled in Options); + IsDownIndex := (AColumn = FDownIndex) and not FCheckBoxHit; + + if (coShowDropMark in FOptions) and (AColumn = FDropTarget) and (AColumn <> FDragIndex) then + begin + if FDropBefore then + DropMark := dmmLeft + else + DropMark := dmmRight; + end + else + DropMark := dmmNone; + + //Fix for issue 643 + //Do not show the left drop mark if the position to drop is just preceding the target which means + //the dragged column will stay where it is + if (DropMark = dmmLeft) and (Items[FDragIndex].Position = TColumnPosition(Max(Integer(Items[FDropTarget].Position) - 1, 0))) + then + DropMark := dmmNone + else + //Do not show the right drop mark if the position to drop is just following the target which means + //the dragged column will stay where it is + if (DropMark = dmmRight) and (Items[FDragIndex].Position = Items[FDropTarget].Position + 1) + then + DropMark := dmmNone; + + IsEnabled := (coEnabled in FOptions) and (TreeViewControl.Enabled); + ShowHeaderGlyph := (hoShowImages in Header.Options) and ((Assigned(Images) and (FImageIndex > - 1)) or FCheckBox); + ShowSortGlyph := (AColumn = Header.SortColumn) and (hoShowSortGlyphs in Header.Options); + WrapCaption := coWrapCaption in FOptions; + + PaintRectangle := ATargetRect; + + // This path for text columns or advanced owner draw. + if (Style = vsText) or not OwnerDraw or AdvancedOwnerDraw then + begin + // See if the application wants to draw part of the header itself. + RequestedElements := []; + if AdvancedOwnerDraw then + begin + PaintInfo.Column := Items[AColumn]; + TreeViewControl.DoHeaderDrawQueryElements(PaintInfo, RequestedElements); + end; + + if ShowRightBorder or (AColumn < Count - 1) then + RightBorderFlag := BF_RIGHT + else + RightBorderFlag := 0; + + if hpeBackground in RequestedElements then + TreeViewControl.DoAdvancedHeaderDraw(PaintInfo, [hpeBackground]) + else + begin + if (tsUseThemes in TreeViewControl.TreeStates) or (TreeViewControl.VclStyleEnabled and (seClient in TreeViewControl.StyleElements)) then + begin + if IsDownIndex then + Details := StyleServices.GetElementDetails(thHeaderItemPressed) + else + if IsHoverIndex then + Details := StyleServices.GetElementDetails(thHeaderItemHot) + else + Details := StyleServices.GetElementDetails(thHeaderItemNormal); + StyleServices.DrawElement(TargetCanvas.Handle, Details, PaintRectangle, @PaintRectangle{$IF CompilerVersion >= 34}, TreeViewControl.CurrentPPI{$IFEND}); + {$IF CompilerVersion >= 34} + if TreeViewControl.CurrentPPI >= 144 then // Fixes issue #1172 + begin + PaintRectangle.Right := PaintRectangle.Right - 1; // For screens with scaled at 150% or more use a splitter with two pixels width + StyleServices.DrawElement(TargetCanvas.Handle, Details, PaintRectangle, @PaintRectangle, TreeViewControl.CurrentPPI); + end; + {$IFEND} + end + else + begin // Windows classic mode + if IsDownIndex then + DrawEdge(TargetCanvas.Handle, PaintRectangle, PressedButtonStyle, PressedButtonFlags) + else + // Plates have the special case of raising on mouse over. + if (Header.Style = hsPlates) and IsHoverIndex and + (coAllowClick in FOptions) and (coEnabled in FOptions) then + DrawEdge(TargetCanvas.Handle, PaintRectangle, RaisedButtonStyle, + RaisedButtonFlags or RightBorderFlag) + else + DrawEdge(TargetCanvas.Handle, PaintRectangle, NormalButtonStyle, + NormalButtonFlags or RightBorderFlag); + end; + end; + + PaintRectangle := ATargetRect; + + // calculate text and glyph position + InflateRect(PaintRectangle, - cMargin, - cMargin); + DrawFormat := DT_TOP or DT_NOPREFIX; + case CaptionAlignment of + taLeftJustify : + DrawFormat := DrawFormat or DT_LEFT; + taRightJustify : + DrawFormat := DrawFormat or DT_RIGHT; + taCenter : + DrawFormat := DrawFormat or DT_CENTER; + end; + if UseRightToLeftReading then + DrawFormat := DrawFormat + DT_RTLREADING; + ComputeHeaderLayout(PaintInfo, DrawFormat); + + // Move glyph and text one pixel to the right and down to simulate a pressed button. + if IsDownIndex then + begin + OffsetRect(TextRectangle, cDownOffset, cDownOffset); + Inc(GlyphPos.X); + Inc(GlyphPos.Y); + Inc(SortGlyphPos.X); + Inc(SortGlyphPos.Y); + end; + + // Advanced owner draw allows to paint elements, which would normally not be painted (because of space + // limitations, empty captions etc.). + ActualElements := RequestedElements * [hpeHeaderGlyph, hpeSortGlyph, hpeDropMark, hpeText, hpeOverlay]; + + // main glyph + FHasImage := False; + if Assigned(Images) then + ImageWidth := Images.Width + else + ImageWidth := 0; + + if not (hpeHeaderGlyph in ActualElements) and ShowHeaderGlyph and + (not ShowSortGlyph or (FBiDiMode <> bdLeftToRight) or (GlyphPos.X + ImageWidth <= SortGlyphPos.X)) then + begin + if not FCheckBox then + begin + ColImageInfo.Images := Images; + Images.Draw(TargetCanvas, GlyphPos.X, GlyphPos.Y, FImageIndex, IsEnabled); + end + else + begin + with TreeViewControl do + begin + ColImageInfo.Images := CheckImages; + ColImageInfo.Index := GetCheckImage(nil, FCheckType, FCheckState, IsEnabled); + ColImageInfo.XPos := GlyphPos.X; + ColImageInfo.YPos := GlyphPos.Y; + PaintCheckImage(TargetCanvas, ColImageInfo, False); + end; + end; + + FHasImage := True; + FImageRect.Left := GlyphPos.X; + FImageRect.Top := GlyphPos.Y; + FImageRect.Right := FImageRect.Left + ColImageInfo.Images.Width; + FImageRect.Bottom := FImageRect.Top + ColImageInfo.Images.Height; + end; + + // caption + if WrapCaption then + ColCaptionText := FCaptionText + else + ColCaptionText := Text; + if IsHoverIndex and TreeViewControl.VclStyleEnabled then + DrawHot := True + else + DrawHot := (IsHoverIndex and (hoHotTrack in Header.Options) and not (tsUseThemes in TreeViewControl.TreeStates)); + if not (hpeText in ActualElements) and (Length(Text) > 0) then + DrawButtonText(TargetCanvas.Handle, ColCaptionText, TextRectangle, IsEnabled, DrawHot, DrawFormat, WrapCaption); + + // sort glyph + if not (hpeSortGlyph in ActualElements) and ShowSortGlyph then + begin + if tsUseExplorerTheme in TreeViewControl.TreeStates then + begin + Pos.TopLeft := SortGlyphPos; + Pos.Right := Pos.Left + SortGlyphSize.cx; + Pos.Bottom := Pos.Top + SortGlyphSize.cy; + if Header.SortDirection = sdAscending then + Glyph := thHeaderSortArrowSortedUp + else + Glyph := thHeaderSortArrowSortedDown; + Details := StyleServices.GetElementDetails(Glyph); + if not StyleServices.DrawElement(TargetCanvas.Handle, Details, Pos, @Pos {$IF CompilerVersion >= 34}, TreeViewControl.CurrentPPI {$IFEND}) then + PaintInfo.DrawSortArrow(Header.SortDirection); + end + else + begin + PaintInfo.DrawSortArrow(Header.SortDirection); + end; + end; + + // Show an indication if this column is the current drop target in a header drag operation. + if not (hpeDropMark in ActualElements) and (DropMark <> dmmNone) then + begin + PaintInfo.DrawDropMark(); + end; + + if ActualElements <> [] then + begin + SavedDC := SaveDC(TargetCanvas.Handle); + TreeViewControl.DoAdvancedHeaderDraw(PaintInfo, ActualElements); + RestoreDC(TargetCanvas.Handle, SavedDC); + end; + end + else // Let application draw the header. + TreeViewControl.DoHeaderDraw(TargetCanvas, Items[AColumn], PaintRectangle, IsHoverIndex, IsDownIndex, + DropMark); + end; + end; + + //--------------- end local functions --------------------------------------- + +var + TargetRect : TRect; + MaxX : TDimension; + Count: Integer; + EndCol: TColumnIndex; +begin + if IsRectEmpty(R) then + Exit; + + // If both draw posibillities are specified then prefer the advanced way. + AdvancedOwnerDraw := (hoOwnerDraw in Header.Options) and Assigned(TreeViewControl.OnAdvancedHeaderDraw) and + Assigned(TreeViewControl.OnHeaderDrawQueryElements) and not (csDesigning in TreeViewControl.ComponentState); + OwnerDraw := (hoOwnerDraw in Header.Options) and Assigned(TreeViewControl.OnHeaderDraw) and + not (csDesigning in TreeViewControl.ComponentState) and not AdvancedOwnerDraw; + + ZeroMemory(@PaintInfo, SizeOf(PaintInfo)); + PaintInfo.TargetCanvas := TargetCanvas; + + with PaintInfo, TargetCanvas do + begin + // Use shortcuts for the images and the font. + Images := Header.Images; + Font := Header.Font; + + PrepareButtonStyles; + + // At first, query the application which parts of the header it wants to draw on its own. + RequestedElements := []; + if AdvancedOwnerDraw then + begin + PaintRectangle := R; + Column := nil; + TreeViewControl.DoHeaderDrawQueryElements(PaintInfo, RequestedElements); + end; + + // Draw the background. + DrawBackground; + + // Now that we have drawn the background, we apply the header's dimensions to R. + R := Rect(Max(R.Left, 0), Max(R.Top, 0), Min(R.Right, TotalWidth), Min(R.Bottom, Header.Height)); + + // Determine where to stop. + MaxX := Target.X + R.Right - R.Left + //Fixes issues #544, #427 -- MaxX should also shift on BidiMode bdRightToLeft + + RTLOffset; //added for fix + + // Determine the start column. + Run := ColumnFromPosition(Point(R.Left + RTLOffset, 0), False); + if Run <= NoColumn then + Exit; + + TargetRect.Top := Target.Y; + TargetRect.Bottom := Target.Y + R.Bottom - R.Top; + TargetRect.Left := Target.X - R.Left + Items[Run].FLeft + RTLOffset; + // TargetRect.Right will be set in the loop + + ShowRightBorder := (Header.Style = hsThickButtons) or not (hoAutoResize in Header.Options) or (TreeViewControl.BevelKind = TBevelKind.bkNone); + + // Now go for each button. + while (Run > NoColumn) and (TargetRect.Left < MaxX) do + begin + + //let application decide how many columns can be spanned + Count:= 1; + TreeViewControl.DoColumnHeaderSpanning(Run, Count); + + if Count > FHeader.Columns.Count then Count := FHeader.Columns.Count; + if Count < 1 then Count := 1; + + EndCol:= Run; + TargetRect.Right := TargetRect.Left; + repeat + Inc(TargetRect.Right, Items[EndCol].Width); + Dec(Count); + EndCol := GetNextVisibleColumn(EndCol); + until (Count = 0) or (EndCol <= NoColumn); + + // create a clipping rect to limit painting to button area + ClipCanvas(TargetCanvas, Rect(Max(TargetRect.Left, Target.X), Target.Y + R.Top, + Min(TargetRect.Right, MaxX), TargetRect.Bottom)); + + PaintColumnHeader(Run, TargetRect); + + SelectClipRgn(Handle, 0); + + TargetRect.Left := TargetRect.Right; + + Run := EndCol; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualTreeColumns.SaveToStream(const Stream : TStream); + +var + I : Integer; + +begin + I := Count; + Stream.WriteBuffer(I, SizeOf(I)); + if I > 0 then + begin + for I := 0 to Count - 1 do + TVirtualTreeColumn(Items[I]).SaveToStream(Stream); + + Stream.WriteBuffer(FPositionToIndex[0], Count * SizeOf(TColumnIndex)); + end; + + // Data introduced with header stream version 5. + Stream.WriteBuffer(DefaultWidth, SizeOf(DefaultWidth)); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualTreeColumns.TotalWidth : TDimension; + +var + LastColumn : TColumnIndex; + +begin + Result := 0; + if (Count > 0) and (Length(FPositionToIndex) > 0) then + begin + LastColumn := FPositionToIndex[Count - 1]; + if not (coVisible in Items[LastColumn].Options) then + LastColumn := GetPreviousVisibleColumn(LastColumn); + if LastColumn > NoColumn then + with Items[LastColumn] do + Result := FLeft + FWidth; + end; +end; + +{ THeaderPaintInfo } + +procedure THeaderPaintInfo.DrawDropMark(); +var + Y : TDimension; + lArrowWidth : TDimension; +begin + lArrowWidth := TBaseVirtualTreeCracker(Self.Column.TreeViewControl).ScaledPixels(5); + Y := Divide(PaintRectangle.Top + PaintRectangle.Bottom - 3 * lArrowWidth, 2); + if DropMark = dmmLeft then + DrawArrow(TargetCanvas, TScrollDirection.sdLeft, Point(PaintRectangle.Left, Y), lArrowWidth) + else + DrawArrow(TargetCanvas, TScrollDirection.sdRight, Point(PaintRectangle.Right - lArrowWidth - Divide(lArrowWidth, 2) {spacing}, Y), lArrowWidth); +end; + +procedure THeaderPaintInfo.DrawSortArrow(pDirection : TSortDirection); +const + cDirection : array [TSortDirection] of TScrollDirection = (TScrollDirection.sdUp, TScrollDirection.sdDown); +var + lOldColor : TColor; +begin + lOldColor := TargetCanvas.Pen.Color; + TargetCanvas.Pen.Color := clDkGray; + DrawArrow(TargetCanvas, cDirection[pDirection], Point(SortGlyphPos.X, SortGlyphPos.Y), SortGlyphSize.cy); + TargetCanvas.Pen.Color := lOldColor; +end; + +{ TVirtualTreeColumnHelper } + +function TVirtualTreeColumnHelper.Header : TVTHeader; +begin + Result := Owner.Header; +end; + +function TVirtualTreeColumnHelper.TreeViewControl : TBaseVirtualTreeCracker; +begin + Result := TBaseVirtualTreeCracker(Owner.Header.GetOwner); +end; + +{ TVirtualTreeColumnsHelper } + +function TVirtualTreeColumnsHelper.TreeViewControl : TBaseVirtualTreeCracker; +begin + Result := TBaseVirtualTreeCracker(Header.GetOwner); +end; + + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.HeaderPopup.pas b/components/virtualtreeview/Source/VirtualTrees.HeaderPopup.pas index 6d003bca1..351cbcec8 100644 --- a/components/virtualtreeview/Source/VirtualTrees.HeaderPopup.pas +++ b/components/virtualtreeview/Source/VirtualTrees.HeaderPopup.pas @@ -68,7 +68,8 @@ interface uses System.Classes, Vcl.Menus, - VirtualTrees; + VirtualTrees.Types, + VirtualTrees.BaseTree; type TVTHeaderPopupOption = ( @@ -78,7 +79,7 @@ interface ); TVTHeaderPopupOptions = set of TVTHeaderPopupOption; - TColumnChangeEvent = procedure(const Sender: TBaseVirtualTree; const Column: TColumnIndex; Visible: Boolean) of object; + TColumnChangeEvent = procedure(const Sender: TObject; const Column: TColumnIndex; Visible: Boolean) of object; TVTHeaderPopupMenu = class(TPopupMenu) strict private @@ -91,10 +92,10 @@ TVTHeaderPopupMenu = class(TPopupMenu) strict protected procedure DoAddHeaderPopupItem(const Column: TColumnIndex; out Cmd: TAddPopupItemType); virtual; procedure DoColumnChange(Column: TColumnIndex; Visible: Boolean); virtual; - procedure OnMenuItemClick(Sender: TObject); + procedure OnMenuItemClick(Sender: TObject); virtual; public constructor Create(AOwner: TComponent); override; - procedure Popup(x, y: Integer); override; + procedure Popup(x, y: TDimension); override; published property Options: TVTHeaderPopupOptions read FOptions write FOptions default [poResizeToFitItem]; @@ -107,7 +108,9 @@ TVTHeaderPopupMenu = class(TPopupMenu) implementation uses - Winapi.Windows, System.Types; + Winapi.Windows, + System.Types, + VirtualTrees.Header; resourcestring sResizeColumnToFit = 'Size &Column to Fit'; @@ -163,7 +166,7 @@ procedure TVTHeaderPopupMenu.OnMenuItemClick(Sender: TObject); //---------------------------------------------------------------------------------------------------------------------- -procedure TVTHeaderPopupMenu.Popup(x, y: Integer); +procedure TVTHeaderPopupMenu.Popup(x, y: TDimension); var ColPos: TColumnPosition; ColIdx: TColumnIndex; @@ -212,7 +215,7 @@ procedure TVTHeaderPopupMenu.Popup(x, y: Integer); with Columns[ColIdx] do begin if coVisible in Options then - Inc(VisibleCounter); + System.Inc(VisibleCounter); DoAddHeaderPopupItem(ColIdx, Cmd); if Cmd <> apHidden then begin @@ -270,4 +273,3 @@ constructor TVTMenuItem.Create(AOwner: TComponent; const ACaption: string; AClic end; end. - diff --git a/components/virtualtreeview/Source/VirtualTrees.StyleHooks.pas b/components/virtualtreeview/Source/VirtualTrees.StyleHooks.pas index 0e06a6803..0f9bbd52e 100644 --- a/components/virtualtreeview/Source/VirtualTrees.StyleHooks.pas +++ b/components/virtualtreeview/Source/VirtualTrees.StyleHooks.pas @@ -140,7 +140,9 @@ implementation System.SysUtils, System.Math, System.Types, - VirtualTrees; + VirtualTrees.Header, + VirtualTrees.Types, + VirtualTrees.BaseTree; function VTStyleServices(AControl: TControl = nil): TCustomStyleServices; begin @@ -168,6 +170,9 @@ procedure TVclStyleScrollBarsHook.CalcScrollBarsRect(); BarInfo: TScrollBarInfo; Ret: BOOL; begin + if not Assigned(VertScrollWnd) then // Might happen, when FInitingScrollBars is set, so InitScrollBars did not yet initialize the members + Exit; + BarInfo.cbSize := SizeOf(BarInfo); Ret := GetScrollBarInfo(Handle, Integer(OBJID_VSCROLL), BarInfo); VertScrollWnd.Visible := (seBorder in Control.StyleElements) and Ret and (not (STATE_SYSTEM_INVISIBLE and BarInfo.rgstate[0] <> 0)); @@ -179,6 +184,9 @@ procedure TVclStyleScrollBarsHook.CalcScrollBarsRect(); BarInfo: TScrollBarInfo; Ret: BOOL; begin + if not Assigned(HorzScrollWnd) then // Might happen, when FInitingScrollBars is set, so InitScrollBars did not yet initialize the members + Exit; + BarInfo.cbSize := SizeOf(BarInfo); Ret := GetScrollBarInfo(Handle, Integer(OBJID_HSCROLL), BarInfo); HorzScrollWnd.Visible := (seBorder in Control.StyleElements) and Ret and (not (STATE_SYSTEM_INVISIBLE and BarInfo.rgstate[0] <> 0)); @@ -696,8 +704,6 @@ procedure TVclStyleScrollBarsHook.WMNCLButtonDown(var Msg: TWMMouse); end; //---------------------------------------------------------------------------------------------------------------------- -//---------------------------------------------------------------------------------------------------------------------- - procedure TVclStyleScrollBarsHook.WMNCLButtonUp(var Msg: TWMMouse); var @@ -1083,12 +1089,4 @@ procedure TVclStyleScrollBarsHook.TScrollWindow.WMPaint(var Msg: TWMPaint); end; {$ifend} -initialization - TCustomStyleEngine.RegisterStyleHook(TVirtualStringTree, TVclStyleScrollBarsHook); - TCustomStyleEngine.RegisterStyleHook(TVirtualDrawTree, TVclStyleScrollBarsHook); - -finalization - TCustomStyleEngine.UnRegisterStyleHook(TVirtualStringTree, TVclStyleScrollBarsHook); - TCustomStyleEngine.UnRegisterStyleHook(TVirtualDrawTree, TVclStyleScrollBarsHook); - end. diff --git a/components/virtualtreeview/Source/VirtualTrees.Types.pas b/components/virtualtreeview/Source/VirtualTrees.Types.pas index 72e9834f0..103d7b55f 100644 --- a/components/virtualtreeview/Source/VirtualTrees.Types.pas +++ b/components/virtualtreeview/Source/VirtualTrees.Types.pas @@ -1,8 +1,1722 @@ -unit VirtualTrees.Types; -// Dummy unit to make migeration between V7 and V8 easier. - -interface - -implementation - -end. \ No newline at end of file +unit VirtualTrees.Types; + +interface + +uses + WinApi.ActiveX, + Winapi.Windows, + Winapi.Messages, + System.Types, + System.Classes, + System.SysUtils, + Vcl.Controls, + Vcl.GraphUtil, + Vcl.Themes, + Vcl.Graphics, + Vcl.ImgList, + System.UITypes; // some types moved from Vcl.* to System.UITypes + +{$MINENUMSIZE 1, make enumerations as small as possible} + +const + VTTreeStreamVersion = 3; + VTHeaderStreamVersion = 6; // The header needs an own stream version to indicate changes only relevant to the header. + + CacheThreshold = 2000; // Number of nodes a tree must at least have to start caching and at the same + // time the maximum number of nodes between two cache entries. + FadeAnimationStepCount = 255; // Number of animation steps for hint fading (0..255). + ShadowSize = 5; // Size in pixels of the hint shadow. This value has no influence on Win2K and XP systems + // as those OSes have native shadow support. + cDefaultTextMargin = 4; // The default margin of text + cInitialDefaultNodeHeight= 18; // the default value of the DefualtNodeHeight property + + // Special identifiers for columns. + NoColumn = - 1; + InvalidColumn = - 2; + + // General constants for imagelists + NoImage = -1; // No image is avalable + EmptyImage = -2; // an empty image used as place holder + + // Indices for check state images in the imagelist used for displaying check-marks. + ckEmpty = 0; // an empty image used as place holder + // radio buttons + ckRadioUncheckedNormal = 1; + ckRadioUncheckedHot = 2; + ckRadioUncheckedPressed = 3; + ckRadioUncheckedDisabled = 4; + ckRadioCheckedNormal = 5; + ckRadioCheckedHot = 6; + ckRadioCheckedPressed = 7; + ckRadioCheckedDisabled = 8; + // check boxes + ckCheckUncheckedNormal = 9; + ckCheckUncheckedHot = 10; + ckCheckUncheckedPressed = 11; + ckCheckUncheckedDisabled = 12; + ckCheckCheckedNormal = 13; + ckCheckCheckedHot = 14; + ckCheckCheckedPressed = 15; + ckCheckCheckedDisabled = 16; + ckCheckMixedNormal = 17; + ckCheckMixedHot = 18; + ckCheckMixedPressed = 19; + ckCheckMixedDisabled = 20; + // simple button + ckButtonNormal = 21; + ckButtonHot = 22; + ckButtonPressed = 23; + ckButtonDisabled = 24; + + // Instead using a TTimer class for each of the various events I use Windows timers with messages + // as this is more economical. + ExpandTimer = 1; + EditTimer = 2; + HeaderTimer = 3; + ScrollTimer = 4; + ChangeTimer = 5; + StructureChangeTimer = 6; + SearchTimer = 7; + ThemeChangedTimer = 8; + + ThemeChangedTimerDelay = 500; + + // Virtual Treeview does not need to be subclassed by an eventual Theme Manager instance as it handles + // Windows XP theme painting itself. Hence the special message is used to prevent subclassing. + CM_DENYSUBCLASSING = CM_BASE + 2000; + + // Decoupling message for auto-adjusting the internal edit window. + CM_AUTOADJUST = CM_BASE + 2005; + + // Drag image helpers for Windows 2000 and up. + IID_IDropTargetHelper : TGUID = (D1 : $4657278B; D2 : $411B; D3 : $11D2; D4 : ($83, $9A, $00, $C0, $4F, $D9, $18, $D0)); + IID_IDragSourceHelper : TGUID = (D1 : $DE5BF786; D2 : $477A; D3 : $11D2; D4 : ($83, $9D, $00, $C0, $4F, $D9, $18, $D0)); + IID_IDropTarget : TGUID = (D1 : $00000122; D2 : $0000; D3 : $0000; D4 : ($C0, $00, $00, $00, $00, $00, $00, $46)); + + // VT's own clipboard formats, + // Note: The reference format is used internally to allow to link to a tree reference + // to implement optimized moves and other back references. + CFSTR_VIRTUALTREE = 'Virtual Tree Data'; + CFSTR_VTREFERENCE = 'Virtual Tree Reference'; + CFSTR_VTHEADERREFERENCE = 'Virtual Tree Header Reference'; + CFSTR_HTML = 'HTML Format'; + CFSTR_RTF = 'Rich Text Format'; + CFSTR_RTFNOOBJS = 'Rich Text Format Without Objects'; + CFSTR_CSV = 'CSV'; + + // Help identifiers for exceptions. Application developers are responsible to link them with actual help topics. + hcTFEditLinkIsNil = 2000; + hcTFWrongMoveError = 2001; + hcTFWrongStreamFormat = 2002; + hcTFWrongStreamVersion = 2003; + hcTFStreamTooSmall = 2004; + hcTFCorruptStream1 = 2005; + hcTFCorruptStream2 = 2006; + hcTFClipboardFailed = 2007; + hcTFCannotSetUserData = 2008; + + // Header standard split cursor. + crHeaderSplit = crHSplit deprecated 'Use vrHSplit instead'; + + // Height changing cursor. + crVertSplit = crVSplit deprecated 'Use vrVSplit instead'; + + // chunk IDs + NodeChunk = 1; + BaseChunk = 2; // chunk containing node state, check state, child node count etc. + // this chunk is immediately followed by all child nodes + CaptionChunk = 3; // used by the string tree to store a node's caption + UserChunk = 4; // used for data supplied by the application + +type +{$IFDEF VT_FMX} + TDimension = Single; + PDimension = ^Single; + TNodeHeight = Single; + TVTCursor = TCursor; + TVTDragDataObject = TDragObject; + TVTBackground = TBitmap; + TVTPaintContext = TCanvas; + TVTBrush = TBrush; +{$ELSE} + TDimension = Integer; // Introduced for Firemonkey support, see #841 + PDimension = ^Integer; + TNodeHeight = NativeInt; + TVTCursor = HCURSOR; + TVTDragDataObject = WinApi.ActiveX.IDataObject; + TVTBackground = TPicture; + TVTPaintContext = HDC; + TVTBrush = HBRUSH; +{$ENDIF} + TColumnIndex = {$if CompilerVersion < 36} type {$endif} Integer; // See issue #1276 + TColumnPosition = type Cardinal; + PCardinal = ^Cardinal; + + // The exception used by the trees. + EVirtualTreeError = class(Exception); + + // Limits the speed interval which can be used for auto scrolling (milliseconds). + TAutoScrollInterval = 1 .. 1000; + + TVTScrollIncrement = 1 .. 10000; + + // OLE drag'n drop support + TFormatEtcArray = array of TFormatEtc; + TFormatArray = array of Word; + + // See issue #1270. + // Taken from: https://learn.microsoft.com/en-us/windows/win32/menurc/about-cursors + // To be used with: LoadCursor(0, MAKEINTRESOURCE(TPanningCursor.MoveAll)) + TPanningCursor = ( + MoveAll = 32654, + MoveNS = 32652, + MoveEW = 32653, + MoveN = 32655, + MoveNE = 32660, + MoveE = 32658, + MoveSE = 32662, + MoveS = 32656, + MoveSW = 32661, + MoveW = 32657, + MoveNW = 32659 + ); + + TSmartAutoFitType = ( + smaAllColumns, // consider nodes in view only for all columns + smaNoColumn, // consider nodes in view only for no column + smaUseColumnOption // use coSmartResize of the corresponding column + ); // describes the used column resize behaviour for AutoFitColumns + + TAddPopupItemType = ( + apNormal, + apDisabled, + apHidden + ); + + TCheckType = ( + ctNone, + ctTriStateCheckBox, + ctCheckBox, + ctRadioButton, + ctButton + ); + + // The check states include both, transient and fluent (temporary) states. The only temporary state defined so + // far is the pressed state. + TCheckState = ( + csUncheckedNormal, // unchecked and not pressed + csUncheckedPressed, // unchecked and pressed + csCheckedNormal, // checked and not pressed + csCheckedPressed, // checked and pressed + csMixedNormal, // 3-state check box and not pressed + csMixedPressed, // 3-state check box and pressed + csUncheckedDisabled, // disabled checkbox, not checkable + csCheckedDisabled, // disabled checkbox, not uncheckable + csMixedDisabled // disabled 3-state checkbox + ); + + /// Adds some convenience methods to type TCheckState + TCheckStateHelper = record helper for TCheckState + strict private + const + // Lookup to quickly convert a specific check state into its pressed counterpart and vice versa. + cPressedState : array [TCheckState] of TCheckState = ( + csUncheckedPressed, csUncheckedPressed, csCheckedPressed, csCheckedPressed, csMixedPressed, csMixedPressed, csUncheckedDisabled, csCheckedDisabled, csMixedDisabled); + cUnpressedState : array [TCheckState] of TCheckState = ( + csUncheckedNormal, csUncheckedNormal, csCheckedNormal, csCheckedNormal, csMixedNormal, csMixedNormal, csUncheckedDisabled, csCheckedDisabled, csMixedDisabled); + cEnabledState : array [TCheckState] of TCheckState = ( + csUncheckedNormal, csUncheckedPressed, csCheckedNormal, csCheckedPressed, csMixedNormal, csMixedPressed, csUncheckedNormal, csCheckedNormal, csMixedNormal); + cToggledState : array [TCheckState] of TCheckState = ( + csCheckedNormal, csCheckedPressed, csUncheckedNormal, csUncheckedPressed, csCheckedNormal, csCheckedPressed, csUncheckedDisabled, csCheckedDisabled, csMixedDisabled); + public + function GetPressed() : TCheckState; inline; + function GetUnpressed() : TCheckState; inline; + function GetEnabled() : TCheckState; inline; + function GetToggled() : TCheckState; inline; + function IsDisabled() : Boolean; inline; + function IsChecked() : Boolean; inline; + function IsUnChecked() : Boolean; inline; + function IsMixed() : Boolean; inline; + end; + +type + // Options per column. + TVTColumnOption = ( + coAllowClick, // Column can be clicked (must be enabled too). + coDraggable, // Column can be dragged. + coEnabled, // Column is enabled. + coParentBidiMode, // Column uses the parent's bidi mode. + coParentColor, // Column uses the parent's background color. + coResizable, // Column can be resized. + coShowDropMark, // Column shows the drop mark if it is currently the drop target. + coVisible, // Column is shown. + coAutoSpring, // Column takes part in the auto spring feature of the header (must be resizable too). + coFixed, // Column is fixed and can not be selected or scrolled etc. + coSmartResize, // Column is resized to its largest entry which is in view (instead of its largest + // visible entry). + coAllowFocus, // Column can be focused. + coDisableAnimatedResize, // Column resizing is not animated. + coWrapCaption, // Caption could be wrapped across several header lines to fit columns width. + coUseCaptionAlignment, // Column's caption has its own aligment. + coEditable, // Column can be edited + coStyleColor // Prefer background color of VCL style over TVirtualTreeColumn.Color + ); + TVTColumnOptions = set of TVTColumnOption; + + TVirtualTreeColumnStyle = ( + vsText, + vsOwnerDraw + ); + + TSortDirection = ( + sdAscending, + sdDescending + ); + + TSortDirectionHelper = record helper for VirtualTrees.Types.TSortDirection + strict private + const + cSortDirectionToInt : Array [TSortDirection] of Integer = (1, - 1); + public + /// Returns +1 for ascending and -1 for descending sort order. + function ToInt() : Integer; inline; + end; + +// Used during owner draw of the header to indicate which drop mark for the column must be drawn. + TVTDropMarkMode = ( + dmmNone, + dmmLeft, + dmmRight + ); + + // auto scroll directions + TScrollDirections = set of TScrollDirection; +// sdLeft, +// sdUp, +// sdRight, +// sdDown +// ); + + + + // There is a heap of switchable behavior in the tree. Since published properties may never exceed 4 bytes, + // which limits sets to at most 32 members, and because for better overview tree options are splitted + // in various sub-options and are held in a commom options class. + // + // Options to customize tree appearance: + TVTPaintOption = ( + toHideFocusRect, // Avoid drawing the dotted rectangle around the currently focused node. + toHideSelection, // Selected nodes are drawn as unselected nodes if the tree is unfocused. + toHotTrack, // Track which node is under the mouse cursor. + toPopupMode, // Paint tree as would it always have the focus (useful for tree combo boxes etc.) + toShowBackground, // Use the background image if there's one. + toShowButtons, // Display collapse/expand buttons left to a node. + toShowDropmark, // Show the dropmark during drag'n drop operations. + toShowHorzGridLines, // Display horizontal lines to simulate a grid. + toShowRoot, // Show lines also at top level (does not show the hidden/internal root node). + toShowTreeLines, // Display tree lines to show hierarchy of nodes. + toShowVertGridLines, // Display vertical lines (depending on columns) to simulate a grid. + toThemeAware, // Draw UI elements (header, tree buttons etc.) according to the current theme if enabled (Windows XP+ only, application must be themed). + toUseBlendedImages, // Enable alpha blending for ghosted nodes or those which are being cut/copied. + toGhostedIfUnfocused, // Ghosted images are still shown as ghosted if unfocused (otherwise the become non-ghosted images). + toFullVertGridLines, // Display vertical lines over the full client area, not only the space occupied by nodes. + // This option only has an effect if toShowVertGridLines is enabled too. + toAlwaysHideSelection, // Do not draw node selection, regardless of focused state. + toUseBlendedSelection, // Enable alpha blending for node selections. + toStaticBackground, // Show simple static background instead of a tiled one. + toChildrenAbove, // Display child nodes above their parent. + toFixedIndent, // Draw the tree with a fixed indent. + toUseExplorerTheme, // Use the explorer theme if run under Windows Vista (or above). + toHideTreeLinesIfThemed, // Do not show tree lines if theming is used. + toShowFilteredNodes // Draw nodes even if they are filtered out. + ); + TVTPaintOptions = set of TVTPaintOption; + + { Options to toggle animation support: + **Do not use toAnimatedToggle when a background image is used for the tree. + The animation does not look good as the image splits and moves with it. + } + TVTAnimationOption = ( + toAnimatedToggle, // Expanding and collapsing a node is animated (quick window scroll). + // **See note above. + toAdvancedAnimatedToggle // Do some advanced animation effects when toggling a node. + ); + TVTAnimationOptions = set of TVTAnimationOption; + + // Options which toggle automatic handling of certain situations: + TVTAutoOption = ( + toAutoDropExpand, // Expand node if it is the drop target for more than a certain time. + toAutoExpand, // Nodes are expanded (collapsed) when getting (losing) the focus. + toAutoScroll, // Scroll if mouse is near the border while dragging or selecting. + toAutoScrollOnExpand, // Scroll as many child nodes in view as possible after expanding a node. + toAutoSort, // Sort tree when Header.SortColumn or Header.SortDirection change or sort node if + // child nodes are added. Sorting will take place also if SortColum is NoColumn (-1). + + toAutoSpanColumns, // Large entries continue into next column(s) if there's no text in them (no clipping). + toAutoTristateTracking, // Checkstates are automatically propagated for tri state check boxes. + toAutoHideButtons, // Node buttons are hidden when there are child nodes, but all are invisible. + toAutoDeleteMovedNodes, // Delete nodes which where moved in a drag operation (if not directed otherwise). + toDisableAutoscrollOnFocus, // Disable scrolling a node or column into view if it gets focused. + toAutoChangeScale, // Change default node height and header height automatically according to the height of the used font. + toAutoFreeOnCollapse, // Frees any child node after a node has been collapsed (HasChildren flag stays there). + toDisableAutoscrollOnEdit, // Do not center a node horizontally when it is edited. + toAutoBidiColumnOrdering // When set then columns (if any exist) will be reordered from lowest index to highest index + // and vice versa when the tree's bidi mode is changed. + ); + TVTAutoOptions = set of TVTAutoOption; + + // Options which determine the tree's behavior when selecting nodes: + TVTSelectionOption = ( + toDisableDrawSelection, // Prevent user from selecting with the selection rectangle in multiselect mode. + toExtendedFocus, // Entries other than in the main column can be selected, edited etc. + toFullRowSelect, // Hit test as well as selection highlight are not constrained to the text of a node. + toLevelSelectConstraint, // Constrain selection to the same level as the selection anchor. + toMiddleClickSelect, // Allow selection, dragging etc. with the middle mouse button. This and toWheelPanning + // are mutual exclusive. + toMultiSelect, // Allow more than one node to be selected. + toRightClickSelect, // Allow selection, dragging etc. with the right mouse button. + toSiblingSelectConstraint, // Constrain selection to nodes with same parent. + toCenterScrollIntoView, // Center nodes vertically in the client area when scrolling into view. + toSimpleDrawSelection, // Simplifies draw selection, so a node's caption does not need to intersect with the + // selection rectangle. + toAlwaysSelectNode, // If this flag is set to true, the tree view tries to always have a node selected. + // This behavior is closer to the Windows TreeView and useful in Windows Explorer style applications. + toRestoreSelection, // Set to true if upon refill the previously selected nodes should be selected again. + // The nodes will be identified by its caption (text in MainColumn) + // You may use TVTHeader.RestoreSelectiuonColumnIndex to define an other column that should be used for indentification. + toSyncCheckboxesWithSelection, // If checkboxes are shown, they follow the change in selections. When checkboxes are + // changed, the selections follow them and vice-versa. + // **Only supported for ctCheckBox type checkboxes. + toSelectNextNodeOnRemoval // If the selected node gets deleted, automatically select the next node. + ); + TVTSelectionOptions = set of TVTSelectionOption; + + TVTEditOptions = ( + toDefaultEdit, // Standard behaviour for end of editing (after VK_RETURN stay on edited cell). + toVerticalEdit, // After VK_RETURN switch to next column. + toHorizontalEdit // After VK_RETURN switch to next row. + ); + + // Options which do not fit into any of the other groups: + TVTMiscOption = ( + toAcceptOLEDrop, // Register tree as OLE accepting drop target + toCheckSupport, // Show checkboxes/radio buttons. + toEditable, // Node captions can be edited. + toFullRepaintOnResize, // Fully invalidate the tree when its window is resized (CS_HREDRAW/CS_VREDRAW). + toGridExtensions, // Use some special enhancements to simulate and support grid behavior. + toInitOnSave, // Initialize nodes when saving a tree to a stream. + toReportMode, // Tree behaves like TListView in report mode. + toToggleOnDblClick, // Toggle node expansion state when it is double clicked. + toWheelPanning, // Support for mouse panning (wheel mice only). This option and toMiddleClickSelect are + // mutal exclusive, where panning has precedence. + toReadOnly, // The tree does not allow to be modified in any way. No action is executed and + // node editing is not possible. + toVariableNodeHeight, // When set then GetNodeHeight will trigger OnMeasureItem to allow variable node heights. + toFullRowDrag, // Start node dragging by clicking anywhere in it instead only on the caption or image. + // Must be used together with toDisableDrawSelection. + toNodeHeightResize, // Allows changing a node's height via mouse. + toNodeHeightDblClickResize, // Allows to reset a node's height to FDefaultNodeHeight via a double click. + toEditOnClick, // Editing mode can be entered with a single click + toEditOnDblClick, // Editing mode can be entered with a double click + toReverseFullExpandHotKey // Used to define Ctrl+'+' instead of Ctrl+Shift+'+' for full expand (and similar for collapsing) + ); + TVTMiscOptions = set of TVTMiscOption; + + // Options to control data export + TVTExportMode = ( + emAll, // export all records (regardless checked state) + emChecked, // export checked records only + emUnchecked, // export unchecked records only + emVisibleDueToExpansion, // Do not export nodes that are not visible because their parent is not expanded + emSelected // export selected nodes only + ); + + // Describes the type of text to return in the text and draw info retrival events. + TVSTTextType = ( + ttNormal, // normal label of the node, this is also the text which can be edited + ttStatic // static (non-editable) text after the normal text + ); + + // Options regarding strings (useful only for the string tree and descendants): + TVTStringOption = ( + toSaveCaptions, // If set then the caption is automatically saved with the tree node, regardless of what is + // saved in the user data. + toShowStaticText, // Show static text in a caption which can be differently formatted than the caption + // but cannot be edited. + toAutoAcceptEditChange // Automatically accept changes during edit if the user finishes editing other then + // VK_RETURN or ESC. If not set then changes are cancelled. + ); + TVTStringOptions = set of TVTStringOption; + + // Be careful when adding new states as this might change the size of the type which in turn + // changes the alignment in the node record as well as the stream chunks. + // Do not reorder the states and always add new states at the end of this enumeration in order to avoid + // breaking existing code. + TVirtualNodeState = ( + vsInitialized, // Set after the node has been initialized. + vsChecking, // Node's check state is changing, avoid propagation. + vsCutOrCopy, // Node is selected as cut or copy and paste source. + vsDisabled, // Set if node is disabled. + vsDeleting, // Set when the node is about to be freed. + vsExpanded, // Set if the node is expanded. + vsHasChildren, // Indicates the presence of child nodes without actually setting them. + vsVisible, // Indicate whether the node is visible or not (independant of the expand states of its parents). + vsSelected, // Set if the node is in the current selection. + vsOnFreeNodeCallRequired, // Set if user data has been set which requires OnFreeNode. + vsAllChildrenHidden, // Set if vsHasChildren is set and no child node has the vsVisible flag set. + vsReleaseCallOnUserDataRequired, // Indicates that the user data is a reference to an interface which should be released. + vsMultiline, // Node text is wrapped at the cell boundaries instead of being shorted. + vsHeightMeasured, // Node height has been determined and does not need a recalculation. + vsToggling, // Set when a node is expanded/collapsed to prevent recursive calls. + vsFiltered, // Indicates that the node should not be painted (without effecting its children). + vsInitializing // Set when the node is being initialized + ); + TVirtualNodeStates = set of TVirtualNodeState; + + // States used in InitNode to indicate states a node shall initially have. + TVirtualNodeInitState = ( + ivsDisabled, + ivsExpanded, + ivsHasChildren, + ivsMultiline, + ivsSelected, + ivsFiltered, + ivsReInit + ); + TVirtualNodeInitStates = set of TVirtualNodeInitState; + + // Various events must be handled at different places than they were initiated or need + // a persistent storage until they are reset. + TVirtualTreeStates = set of ( + tsChangePending, // A selection change is pending. + tsCheckPropagation, // Set during automatic check state propagation. + tsCollapsing, // A full collapse operation is in progress. + tsToggleFocusedSelection, // Node selection was modifed using Ctrl-click. Change selection state on next mouse up. + tsClearPending, // Need to clear the current selection on next mouse move. + tsClearOnNewSelection, // Need to clear the current selection before selecting a new node + tsClipboardFlushing, // Set during flushing the clipboard to avoid freeing the content. + tsCopyPending, // Indicates a pending copy operation which needs to be finished. + tsCutPending, // Indicates a pending cut operation which needs to be finished. + tsDrawSelPending, // Multiselection only. User held down the left mouse button on a free + // area and might want to start draw selection. + tsDrawSelecting, // Multiselection only. Draw selection has actually started. + tsEditing, // Indicates that an edit operation is currently in progress. + tsEditPending, // An mouse up start edit if dragging has not started. + tsExpanding, // A full expand operation is in progress. + tsNodeHeightTracking, // A node height changing operation is in progress. + tsNodeHeightTrackPending, // left button is down, user might want to start changing a node's height. + tsHint, // Set when our hint is visible or soon will be. + tsInAnimation, // Set if the tree is currently in an animation loop. + tsIncrementalSearching, // Set when the user starts incremental search. + tsIncrementalSearchPending, // Set in WM_KEYDOWN to tell to use the char in WM_CHAR for incremental search. + tsIterating, // Set when IterateSubtree is currently in progress. + tsLeftButtonDown, // Set when the left mouse button is down. + tsLeftDblClick, // Set when the left mouse button was doubly clicked. + tsMiddleButtonDown, // Set when the middle mouse button is down. + tsMiddleDblClick, // Set when the middle mouse button was doubly clicked. + tsNeedRootCountUpdate, // Set if while loading a root node count is set. + tsOLEDragging, // OLE dragging in progress. + tsOLEDragPending, // User has requested to start delayed dragging. + tsPainting, // The tree is currently painting itself. + tsRightButtonDown, // Set when the right mouse button is down. + tsRightDblClick, // Set when the right mouse button was doubly clicked. + tsPopupMenuShown, // The user clicked the right mouse button, which might cause a popup menu to appear. + tsScrolling, // Set when autoscrolling is active. + tsScrollPending, // Set when waiting for the scroll delay time to elapse. + tsSizing, // Set when the tree window is being resized. This is used to prevent recursive calls + // due to setting the scrollbars when sizing. + tsStopValidation, // Cache validation can be stopped (usually because a change has occured meanwhile). + tsStructureChangePending, // The structure of the tree has been changed while the update was locked. + tsSynchMode, // Set when the tree is in synch mode, where no timer events are triggered. + tsThumbTracking, // Stop updating the horizontal scroll bar while dragging the vertical thumb and vice versa. + tsToggling, // A toggle operation (for some node) is in progress. + tsUpdateHiddenChildrenNeeded, // Pending update for the hidden children flag after massive visibility changes. + tsUseCache, // The tree's node caches are validated and non-empty. + tsUserDragObject, // Signals that the application created an own drag object in OnStartDrag. + tsUseThemes, // The tree runs under WinXP+, is theme aware and themes are enabled. + tsValidating, // The tree's node caches are currently validated. + tsPreviouslySelectedLocked,// The member FPreviouslySelected should not be changed + tsValidationNeeded, // Something in the structure of the tree has changed. The cache needs validation. + tsVCLDragging, // VCL drag'n drop in progress. + tsVCLDragPending, // One-shot flag to avoid clearing the current selection on implicit mouse up for VCL drag. + tsVCLDragFinished, // Flag to avoid triggering the OnColumnClick event twice + tsPanning, // Mouse panning is active. + tsWindowCreating, // Set during window handle creation to avoid frequent unnecessary updates. + tsUseExplorerTheme // The tree runs under WinVista+ and is using the explorer theme + ); + + + TCheckImageKind = ( + ckCustom, // application defined check images + ckSystemDefault // Uses the system check images, theme aware. + ); + + // mode to describe a move action + TVTNodeAttachMode = ( + amNoWhere, // just for simplified tests, means to ignore the Add/Insert command + amInsertBefore, // insert node just before destination (as sibling of destination) + amInsertAfter, // insert node just after destionation (as sibling of destination) + amAddChildFirst, // add node as first child of destination + amAddChildLast // add node as last child of destination + ); + + // modes to determine drop position further + TDropMode = ( + dmNowhere, + dmAbove, + dmOnNode, + dmBelow + ); + + // operations basically allowed during drag'n drop + TDragOperation = ( + doCopy, + doMove, + doLink + ); + TDragOperations = set of TDragOperation; + + TVTImageKind = ( + ikNormal, + ikSelected, + ikState, + ikOverlay + ); + + { + Fine points: Observed when fixing issue #623 + -- hmHint allows multiline hints automatically if provided through OnGetHint event. + This is irresptive of whether node itself is multi-line or not. + + -- hmToolTip shows a hint only when node text is not fully shown. It's meant to + fully show node text when not visible. It will show multi-line hint only if + the node itself is multi-line. If you provide a custom multi-line hint then + you must force linebreak style to hlbForceMultiLine in the OnGetHint event + in order to show the complete hint. + } + TVTHintMode = ( + hmDefault, // show the hint of the control + hmHint, // show node specific hint string returned by the application + hmHintAndDefault, // same as hmHint but show the control's hint if no node is concerned + hmTooltip // show the text of the node if it isn't already fully shown + ); + + // Indicates how to format a tooltip. + TVTTooltipLineBreakStyle = ( + hlbDefault, // Use multi-line style of the node. + hlbForceSingleLine, // Use single line hint. + hlbForceMultiLine // Use multi line hint. + ); + + TMouseButtons = set of TMouseButton; + + // Used to describe the action to do when using the OnBeforeItemErase event. + TItemEraseAction = ( + eaColor, // Use the provided color to erase the background instead the one of the tree. + eaDefault, // The tree should erase the item's background (bitmap or solid). + eaNone // Do nothing. Let the application paint the background. + ); + + + // Kinds of operations + TVTOperationKind = ( + okAutoFitColumns, + okGetMaxColumnWidth, + okSortNode, + okSortTree, + okExport, + okExpand + ); + TVTOperationKinds = set of TVTOperationKind; + + // Indicates in the OnUpdating event what state the tree is currently in. + TVTUpdateState = ( + usBegin, // The tree just entered the update state (BeginUpdate call for the first time). + usBeginSynch, // The tree just entered the synch update state (BeginSynch call for the first time). + usSynch, // Begin/EndSynch has been called but the tree did not change the update state. + usUpdate, // Begin/EndUpdate has been called but the tree did not change the update state. + usEnd, // The tree just left the update state (EndUpdate called for the last level). + usEndSynch // The tree just left the synch update state (EndSynch called for the last level). + ); + + // These elements are used both to query the application, which of them it wants to draw itself and to tell it during + // painting, which elements must be drawn during the advanced custom draw events. + THeaderPaintElements = set of ( + hpeBackground, + hpeDropMark, + hpeHeaderGlyph, + hpeSortGlyph, + hpeText, + // New in 7.0: Use this in FOnHeaderDrawQueryElements and OnAdvancedHeaderDraw + // for additional custom header drawing while keeping the default drawing + hpeOverlay + ); + + // determines whether and how the drag image is to show + TVTDragImageKind = ( + diComplete, // show a complete drag image with all columns, only visible columns are shown + diMainColumnOnly, // show only the main column (the tree column) + diNoImage // don't show a drag image at all + ); + + // Switch for OLE and VCL drag'n drop. Because it is not possible to have both simultanously. + TVTDragType = ( + dtOLE, + dtVCL + ); + + // Determines the look of a tree's lines that show the hierarchy + TVTLineStyle = ( + lsCustomStyle, // application provides a line pattern + lsDotted, // usual dotted lines (default) + lsSolid // simple solid lines + ); + + // TVTLineType is used during painting a tree for its tree lines that show the hierarchy + TVTLineType = ( + ltNone, // no line at all + ltBottomRight, // a line from bottom to the center and from there to the right + ltTopDown, // a line from top to bottom + ltTopDownRight, // a line from top to bottom and from center to the right + ltRight, // a line from center to the right + ltTopRight, // a line from bottom to center and from there to the right + // special styles for alternative drawings of tree lines + ltLeft, // a line from top to bottom at the left + ltLeftBottom // a combination of ltLeft and a line at the bottom from left to right + ); + + // Determines how to draw tree lines. + TVTLineMode = ( + lmNormal, // usual tree lines (as in TTreeview) + lmBands // looks similar to a Nassi-Schneidermann diagram + ); + + // A collection of line type IDs which is used while painting a node. + TLineImage = array of TVTLineType; + + + // Export type + TVTExportType = ( + etNone, // No export, normal displaying on the screen + etRTF, // contentToRTF + etHTML, // contentToHTML + etText, // contentToText + etExcel, // supported by external tools + etWord, // supported by external tools + etPDF, // supported by external tools + etPrinter,// supported by external tools + etCSV, // supported by external tools + etCustom // supported by external tools + ); + + // Options which are used when modifying the scroll offsets. + TScrollUpdateOptions = set of ( + suoRepaintHeader, // if suoUpdateNCArea is also set then invalidate the header + suoRepaintScrollBars, // if suoUpdateNCArea is also set then repaint both scrollbars after updating them + suoScrollClientArea, // scroll and invalidate the proper part of the client area + suoUpdateNCArea // update non-client area (scrollbars, header) + ); + + // Determines the look of a tree's buttons. + TVTButtonStyle = ( + bsRectangle, // traditional Windows look (plus/minus buttons) + bsTriangle // traditional Macintosh look + ); + + // TButtonFillMode is only used when the button style is bsRectangle and determines how to fill the interior. + TVTButtonFillMode = ( + fmTreeColor, // solid color, uses the tree's background color + fmWindowColor, // solid color, uses clWindow + fmShaded, // no longer supported, use toThemeAware for Windows XP and later instead + fmTransparent // transparent color, use the item's background color + ); + + // Method called by the Animate routine for each animation step. + TVTAnimationCallback = function(Step, StepSize: Integer; Data: Pointer): Boolean of object; + + TVTIncrementalSearch = ( + isAll, // search every node in tree, initialize if necessary + isNone, // disable incremental search + isInitializedOnly, // search only initialized nodes, skip others + isVisibleOnly // search only visible nodes, initialize if necessary + ); + + // Determines which direction to use when advancing nodes during an incremental search. + TVTSearchDirection = ( + sdForward, + sdBackward + ); + + // Determines where to start incremental searching for each key press. + TVTSearchStart = ( + ssAlwaysStartOver, // always use the first/last node (depending on direction) to search from + ssLastHit, // use the last found node + ssFocusedNode // use the currently focused node + ); + + // Determines how to use the align member of a node. + TVTNodeAlignment = ( + naFromBottom, // the align member specifies amount of units (usually pixels) from top border of the node + naFromTop, // align is to be measured from bottom + naProportional // align is to be measure in percent of the entire node height and relative to top + ); + + // Determines how to draw the selection rectangle used for draw selection. + TVTDrawSelectionMode = ( + smDottedRectangle, // same as DrawFocusRect + smBlendedRectangle // alpha blending, uses special colors (see TVTColors) + ); + + // Determines for which purpose the cell paint event is called. + TVTCellPaintMode = ( + cpmPaint, // painting the cell + cpmGetContentMargin // getting cell content margin + ); + + // Determines which sides of the cell content margin should be considered. + TVTCellContentMarginType = ( + ccmtAllSides, // consider all sides + ccmtTopLeftOnly, // consider top margin and left margin only + ccmtBottomRightOnly // consider bottom margin and right margin only + ); + + TChangeReason = ( + crIgnore, // used as placeholder + crAccumulated, // used for delayed changes + crChildAdded, // one or more child nodes have been added + crChildDeleted, // one or more child nodes have been deleted + crNodeAdded, // a node has been added + crNodeCopied, // a node has been duplicated + crNodeMoved // a node has been moved to a new place + ); // desribes what made a structure change event happen + + TChunkHeader = record + ChunkType, + ChunkSize: Integer; // contains the size of the chunk excluding the header + end; + +const + DefaultPaintOptions = [toShowButtons, toShowDropmark, toShowTreeLines, toShowRoot, toThemeAware, toUseBlendedImages, toFullVertGridLines]; + DefaultAnimationOptions = []; + DefaultAutoOptions = [toAutoDropExpand, toAutoTristateTracking, toAutoScrollOnExpand, toAutoDeleteMovedNodes, toAutoChangeScale, toAutoSort, toAutoHideButtons]; + DefaultSelectionOptions = [toSelectNextNodeOnRemoval]; + DefaultMiscOptions = [toAcceptOLEDrop, toFullRepaintOnResize, toInitOnSave, toToggleOnDblClick, toWheelPanning, toEditOnClick]; + + DefaultStringOptions = [toSaveCaptions, toAutoAcceptEditChange]; + +type + TCustomVirtualTreeOptions = class(TPersistent) + private + FOwner : TCustomControl; + FPaintOptions : TVTPaintOptions; + FAnimationOptions : TVTAnimationOptions; + FAutoOptions : TVTAutoOptions; + FSelectionOptions : TVTSelectionOptions; + FMiscOptions : TVTMiscOptions; + FExportMode : TVTExportMode; + FEditOptions : TVTEditOptions; + procedure SetAnimationOptions(const Value : TVTAnimationOptions); + procedure SetAutoOptions(const Value : TVTAutoOptions); + procedure SetMiscOptions(const Value : TVTMiscOptions); + procedure SetPaintOptions(const Value : TVTPaintOptions); + procedure SetSelectionOptions(const Value : TVTSelectionOptions); + protected + // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) + function StyleServices(AControl : TControl = nil) : TCustomStyleServices; + public + constructor Create(AOwner : TCustomControl); virtual; + //these bypass the side effects in the regular setters. + procedure InternalSetMiscOptions(const Value : TVTMiscOptions); + + procedure AssignTo(Dest : TPersistent); override; + property AnimationOptions : TVTAnimationOptions read FAnimationOptions write SetAnimationOptions default DefaultAnimationOptions; + property AutoOptions : TVTAutoOptions read FAutoOptions write SetAutoOptions default DefaultAutoOptions; + property ExportMode : TVTExportMode read FExportMode write FExportMode default emAll; + property MiscOptions : TVTMiscOptions read FMiscOptions write SetMiscOptions default DefaultMiscOptions; + property PaintOptions : TVTPaintOptions read FPaintOptions write SetPaintOptions default DefaultPaintOptions; + property SelectionOptions : TVTSelectionOptions read FSelectionOptions write SetSelectionOptions default DefaultSelectionOptions; + property EditOptions : TVTEditOptions read FEditOptions write FEditOptions default toDefaultEdit; + + property Owner: TCustomControl read FOwner; + end; + + TTreeOptionsClass = class of TCustomVirtualTreeOptions; + + TVirtualTreeOptions = class(TCustomVirtualTreeOptions) + published + property AnimationOptions; + property AutoOptions; + property ExportMode; + property MiscOptions; + property PaintOptions; + property SelectionOptions; + end; + + TCustomStringTreeOptions = class(TCustomVirtualTreeOptions) + private + FStringOptions : TVTStringOptions; + procedure SetStringOptions(const Value : TVTStringOptions); + protected + public + constructor Create(AOwner : TCustomControl); override; + procedure AssignTo(Dest : TPersistent); override; + property StringOptions : TVTStringOptions read FStringOptions write SetStringOptions default DefaultStringOptions; + end; + + TStringTreeOptions = class(TCustomStringTreeOptions) + published + property AnimationOptions; + property AutoOptions; + property ExportMode; + property MiscOptions; + property PaintOptions; + property SelectionOptions; + property StringOptions; + property EditOptions; + end; + + TScrollBarStyle = ( + sbmRegular, + sbm3D + ); + + // A class to manage scroll bar aspects. + TScrollBarOptions = class(TPersistent) + private + FAlwaysVisible : Boolean; + FOwner : TCustomControl; + FScrollBars : TScrollStyle; // used to hide or show vertical and/or horizontal scrollbar + FScrollBarStyle : TScrollBarStyle; // kind of scrollbars to use + FIncrementX, FIncrementY : TVTScrollIncrement; // number of pixels to scroll in one step (when auto scrolling) + procedure SetAlwaysVisible(Value : Boolean); + procedure SetScrollBars(Value : TScrollStyle); + procedure SetScrollBarStyle(Value : TScrollBarStyle); + protected + function GetOwner : TPersistent; override; + public + constructor Create(AOwner : TCustomControl); + + procedure Assign(Source : TPersistent); override; + published + property AlwaysVisible : Boolean read FAlwaysVisible write SetAlwaysVisible default False; + property HorizontalIncrement : TVTScrollIncrement read FIncrementX write FIncrementX default 20; + property ScrollBars : TScrollStyle read FScrollBars write SetScrollBars default TScrollStyle.ssBoth; + property ScrollBarStyle : TScrollBarStyle read FScrollBarStyle write SetScrollBarStyle default sbmRegular; + property VerticalIncrement : TVTScrollIncrement read FIncrementY write FIncrementY default 20; + end; + + PVirtualNode = ^TVirtualNode; + + TVirtualNode = packed record + private + fIndex: Cardinal; // index of node with regard to its parent + fChildCount: Cardinal; // number of child nodes + fNodeHeight: TNodeHeight; // height in pixels + public + States: TVirtualNodeStates; // states describing various properties of the node (expanded, initialized etc.) + Align: Byte; // line/button alignment + CheckState: TCheckState; // indicates the current check state (e.g. checked, pressed etc.) + CheckType: TCheckType; // indicates which check type shall be used for this node + Dummy: Byte; // dummy value to fill DWORD boundary + TotalCount: Cardinal; // sum of this node, all of its child nodes and their child nodes etc. + TotalHeight: TNodeHeight;// height in pixels this node covers on screen including the height of all of its children. + _Filler: TDWordFiller; // Ensure 8 Byte alignment of following pointers for 64bit builds. Issue #1136 + // Note: Some copy routines require that all pointers (as well as the data area) in a node are + // located at the end of the node! Hence if you want to add new member fields (except pointers to internal + // data) then put them before field Parent. + private + fParent: PVirtualNode; // reference to the node's parent (for the root this contains the treeview) + fPrevSibling: PVirtualNode; // link to the node's previous sibling or nil if it is the first node + fNextSibling: PVirtualNode; // link to the node's next sibling or nil if it is the last node + public // still public as it is used as var parameter in MergeSortAscending() + FirstChild: PVirtualNode; // link to the node's first child... + private + fLastChild: PVirtualNode; // link to the node's last child... + public + procedure SetParent(const pParent: PVirtualNode); inline; //internal method, do not call directly but use Parent[Node] := x on tree control. + procedure SetPrevSibling(const pPrevSibling: PVirtualNode); inline; //internal method, do not call directly + procedure SetNextSibling(const pNextSibling: PVirtualNode); inline; //internal method, do not call directly + procedure SetFirstChild(const pFirstChild: PVirtualNode); inline; //internal method, do not call directly + procedure SetLastChild(const pLastChild: PVirtualNode); inline; //internal method, do not call directly + procedure SetIndex(const pIndex: Cardinal); inline; //internal method, do not call directly. + procedure SetChildCount(const pCount: Cardinal); inline; //internal method, do not call directly. + procedure SetNodeHeight(const pNodeHeight: TNodeHeight); inline; //internal method, do not call directly. + property Index: Cardinal read fIndex; + property ChildCount: Cardinal read fChildCount; + property Parent: PVirtualNode read fParent; + property PrevSibling: PVirtualNode read fPrevSibling; + property NextSibling: PVirtualNode read fNextSibling; + property LastChild: PVirtualNode read fLastChild; + property NodeHeight: TNodeHeight read fNodeHeight; + private + Data: record end; // this is a placeholder, each node gets extra data determined by NodeDataSize + public + function IsAssigned(): Boolean; inline; + function GetData(): Pointer; overload; inline; + function GetData(): T; overload; inline; + procedure SetData(pUserData: Pointer); overload; + procedure SetData(pUserData: T); overload; + procedure SetData(const pUserData: IInterface); overload; + end; + + + TVTHeaderColumnLayout = ( + blGlyphLeft, + blGlyphRight, + blGlyphTop, + blGlyphBottom + ); + + // These flags are used to indicate where a click in the header happened. + TVTHeaderHitPosition = ( + hhiNoWhere, // No column is involved (possible only if the tree is smaller than the client area). + hhiOnColumn, // On a column. + hhiOnIcon, // On the bitmap associated with a column. + hhiOnCheckbox // On the checkbox if enabled. + ); + TVTHeaderHitPositions = set of TVTHeaderHitPosition; + + // These flags are returned by the hit test method. + THitPosition = ( + hiAbove, // above the client area (if relative) or the absolute tree area + hiBelow, // below the client area (if relative) or the absolute tree area + hiNowhere, // no node is involved (possible only if the tree is not as tall as the client area) + hiOnItem, // on the bitmaps/buttons or label associated with an item + hiOnItemButton, // on the button associated with an item + hiOnItemButtonExact, // exactly on the button associated with an item + hiOnItemCheckbox, // on the checkbox if enabled + hiOnItemIndent, // in the indentation area in front of a node + hiOnItemLabel, // on the normal text area associated with an item + hiOnItemLeft, // in the area to the left of a node's text area (e.g. when right aligned or centered) + hiOnItemRight, // in the area to the right of a node's text area (e.g. if left aligned or centered) + hiOnNormalIcon, // on the "normal" image + hiOnStateIcon, // on the state image + hiToLeft, // to the left of the client area (if relative) or the absolute tree area + hiToRight, // to the right of the client area (if relative) or the absolute tree area + hiUpperSplitter, // in the upper splitter area of a node + hiLowerSplitter // in the lower splitter area of a node + ); + THitPositions = set of THitPosition; + + // Structure used when info about a certain position in the header is needed. + TVTHeaderHitInfo = record + X, + Y: TDimension; + Button: TMouseButton; + Shift: TShiftState; + Column: TColumnIndex; + HitPosition: TVTHeaderHitPositions; + end; + + // Structure used when info about a certain position in the tree is needed. + THitInfo = record + HitNode: PVirtualNode; + HitPositions: THitPositions; + HitColumn: TColumnIndex; + HitPoint: TPoint; + ShiftState: TShiftState; + end; + + TVTHeaderStyle = ( + hsThickButtons, //TButton look and feel + hsFlatButtons, //flatter look than hsThickButton, like an always raised flat TToolButton + hsPlates //flat TToolButton look and feel (raise on hover etc.) + ); + + TVTHeaderOption = ( + hoAutoResize, //Adjust a column so that the header never exceeds the client width of the owner control. + hoColumnResize, //Resizing columns with the mouse is allowed. + hoDblClickResize, //Allows a column to resize itself to its largest entry. + hoDrag, //Dragging columns is allowed. + hoHotTrack, //Header captions are highlighted when mouse is over a particular column. + hoOwnerDraw, //Header items with the owner draw style can be drawn by the application via event. + hoRestrictDrag, //Header can only be dragged horizontally. + hoShowHint, //Show application defined header hint. + hoShowImages, //Show header images. + hoShowSortGlyphs, //Allow visible sort glyphs. + hoVisible, //Header is visible. + hoAutoSpring, //Distribute size changes of the header to all columns, which are sizable and have the coAutoSpring option enabled. + hoFullRepaintOnResize, //Fully invalidate the header (instead of subsequent columns only) when a column is resized. + hoDisableAnimatedResize, //Disable animated resize for all columns. + hoHeightResize, //Allow resizing header height via mouse. + hoHeightDblClickResize, //Allow the header to resize itself to its default height. + hoHeaderClickAutoSort, //Clicks on the header will make the clicked column the SortColumn or toggle sort direction if it already was the sort column + hoAutoColumnPopupMenu, //Show a context menu for activating and deactivating columns on right click + hoAutoResizeInclCaption //Includes the header caption for the auto resizing + ); + TVTHeaderOptions = set of TVTHeaderOption; + + THeaderState = ( + hsAutoSizing, //auto size chain is in progess, do not trigger again on WM_SIZE + hsDragging, //header dragging is in progress (only if enabled) + hsDragPending, //left button is down, user might want to start dragging a column + hsLoading, //The header currently loads from stream, so updates are not necessary. + hsColumnWidthTracking, //column resizing is in progress + hsColumnWidthTrackPending, //left button is down, user might want to start resize a column + hsHeightTracking, //height resizing is in progress + hsHeightTrackPending, //left button is down, user might want to start changing height + hsResizing, //multi column resizing in progress + hsScaling, //the header is scaled after a change of FixedAreaConstraints or client size + hsNeedScaling //the header needs to be scaled + ); + THeaderStates = set of THeaderState; + + // content elements of the control from left to right, used when calculatin left margins. + TVTElement = ( + ofsMargin, // right of the margin + ofsToggleButton, // the exact x-postition of the toggle button + ofsCheckBox, + ofsStateImage, + ofsImage, + ofsLabel, // where drawing a selection begins + ofsText, // includes TextMargin + ofsRightOfText, // Includes NodeWidth and ExtraNodeWidth + ofsEndOfClientArea // The end of the paint area + ); + + /// An array that can be used to calculate the offsets ofthe elements in the tree. + TVTOffsets = array [TVTElement] of TDimension; + + // For painting a node and its columns/cells a lot of information must be passed frequently around. + TVTImageInfo = record + Index: TImageIndex; // Index in the associated image list. + XPos, // Horizontal position in the current target canvas. + YPos: TDimension; // Vertical position in the current target canvas. + Ghosted: Boolean; // Flag to indicate that the image must be drawn slightly lighter. + Images: TCustomImageList; // The image list to be used for painting. + function Equals(const pImageInfo2: TVTImageInfo): Boolean; + end; + + TVTImageInfoIndex = ( + iiNormal, + iiState, + iiCheck, + iiOverlay + ); + + // options which determine what to draw in PaintTree + TVTInternalPaintOption = ( + poBackground, // draw background image if there is any and it is enabled + poColumnColor, // erase node's background with the column's color + poDrawFocusRect, // draw focus rectangle around the focused node + poDrawSelection, // draw selected nodes with the normal selection color + poDrawDropMark, // draw drop mark if a node is currently the drop target + poGridLines, // draw grid lines if enabled + poMainOnly, // draw only the main column + poSelectedOnly, // draw only selected nodes + poUnbuffered // draw directly onto the target canvas; especially useful when printing + ); + TVTInternalPaintOptions = set of TVTInternalPaintOption; + + TVTPaintInfo = record + Canvas: TCanvas; // the canvas to paint on + PaintOptions: TVTInternalPaintOptions; // a copy of the paint options passed to PaintTree + Node: PVirtualNode; // the node to paint + Column: TColumnIndex; // the node's column index to paint + Position: TColumnPosition; // the column position of the node + CellRect: TRect; // the node cell + ContentRect: TRect; // the area of the cell used for the node's content + NodeWidth: TDimension; // the actual node width + Alignment: TAlignment; // how to align within the node rectangle + CaptionAlignment: TAlignment; // how to align text within the caption rectangle + BidiMode: TBidiMode; // directionality to be used for painting + BrushOrigin: TPoint; // the alignment for the brush used to draw dotted lines + ImageInfo: array[TVTImageInfoIndex] of TVTImageInfo; // info about each possible node image + Offsets: TVTOffsets; // The offsets of the various elements of a tree node + VAlign: TDimension; + procedure AdjustImageCoordinates(); + end; + + TNodeArray = array of PVirtualNode; + +implementation + +uses + System.TypInfo, + VirtualTrees.StyleHooks, + VirtualTrees.BaseTree, + VirtualTrees.BaseAncestorVcl{to eliminate H2443 about inline expanding} + ; + +type + TVTCracker = class(TBaseVirtualTree); + + +{ TVirtualNode } + +function TVirtualNode.GetData(): Pointer; + +// Returns the associated data converted to the class given in the generic part of the function. + +begin + Result := @Self.Data; + Include(States, vsOnFreeNodeCallRequired); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualNode.GetData: T; + +// Returns the associated data converted to the class given in the generic part of the function. + +begin + Result := T(Pointer((PByte(@(Self.Data))))^); + Include(States, vsOnFreeNodeCallRequired); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TVirtualNode.IsAssigned: Boolean; + +// Returns False if this node is nil, True otherwise + +begin + Exit(@Self <> nil); +end; + +procedure TVirtualNode.SetNodeHeight(const pNodeHeight: TNodeHeight); +begin + fNodeHeight := pNodeHeight; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualNode.SetData(pUserData: Pointer); + + + // Can be used to set user data of a PVirtualNode with the size of a pointer, useful for setting + // A pointer to a record or a reference to a class instance. +var + NodeData: PPointer; +begin + NodeData := PPointer(@Self.Data); + NodeData^ := pUserData; + Include(Self.States, vsOnFreeNodeCallRequired); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualNode.SetChildCount(const pCount: Cardinal); +begin + fChildCount := pCount; +end; + +procedure TVirtualNode.SetData(const pUserData: IInterface); + + + // Can be used to set user data of a PVirtualNode to a class instance, + // will take care about reference counting. + +begin + pUserData._AddRef(); + SetData(Pointer(pUserData)); + Include(Self.States, vsReleaseCallOnUserDataRequired); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TVirtualNode.SetData(pUserData: T); + +begin + T(Pointer((PByte(@(Self.Data))))^) := pUserData; + if PTypeInfo(TypeInfo(T)).Kind = tkInterface then + Include(Self.States, vsReleaseCallOnUserDataRequired); + Include(Self.States, vsOnFreeNodeCallRequired); +end; + +procedure TVirtualNode.SetFirstChild(const pFirstChild: PVirtualNode); +begin + FirstChild := pFirstChild; +end; + +procedure TVirtualNode.SetLastChild(const pLastChild: PVirtualNode); +begin + fLastChild := pLastChild; +end; + +procedure TVirtualNode.SetIndex(const pIndex: Cardinal); +begin + fIndex := pIndex; +end; + +procedure TVirtualNode.SetParent(const pParent: PVirtualNode); +begin + fParent := pParent; +end; + +procedure TVirtualNode.SetPrevSibling(const pPrevSibling: PVirtualNode); +begin + fPrevSibling := pPrevSibling; +end; + +procedure TVirtualNode.SetNextSibling(const pNextSibling: PVirtualNode); +begin + fNextSibling := pNextSibling; +end; + +//---------------------------------------------------------------------------------------------------------------------- + + +{ TVTImageInfo } + +function TVTImageInfo.Equals(const pImageInfo2: TVTImageInfo): Boolean; + + // Returns true if both images are the same, does not regard Ghosted and position. + +begin + Result := (Self.Index = pImageInfo2.Index) and (Self.Images = pImageInfo2.Images); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +{ TVTPaintInfo } + +procedure TVTPaintInfo.AdjustImageCoordinates(); +// During painting of the main column some coordinates must be adjusted due to the tree lines. +begin + ContentRect := CellRect; + if BidiMode = bdLeftToRight then + begin + ContentRect.Left := CellRect.Left + Offsets[TVTElement.ofsLabel]; + ImageInfo[iiNormal].XPos := CellRect.Left + Offsets[TVTElement.ofsImage]; + ImageInfo[iiState].XPos := CellRect.Left + Offsets[TVTElement.ofsStateImage]; + ImageInfo[iiCheck].XPos := CellRect.Left + Offsets[TVTElement.ofsCheckBox]; + end + else + begin + /// Since images are still drawn from left to right, we need to substract the image sze as well. + ImageInfo[iiNormal].XPos := CellRect.Right - Offsets[TVTElement.ofsImage] - (Offsets[TVTElement.ofsLabel] - Offsets[TVTElement.ofsImage]); + ImageInfo[iiState].XPos := CellRect.Right - Offsets[TVTElement.ofsStateImage] - (Offsets[TVTElement.ofsImage] - Offsets[TVTElement.ofsStateImage]); + ImageInfo[iiCheck].XPos := CellRect.Right - Offsets[TVTElement.ofsCheckBox] - (Offsets[TVTElement.ofsStateImage] - Offsets[TVTElement.ofsCheckBox]); + ContentRect.Right := CellRect.Right - Offsets[TVTElement.ofsLabel]; + end; + if ImageInfo[iiNormal].Index > -1 then + ImageInfo[iiNormal].YPos := CellRect.Top + VAlign - ImageInfo[iiNormal].Images.Height div 2; + if ImageInfo[iiState].Index > -1 then + ImageInfo[iiState].YPos := CellRect.Top + VAlign - ImageInfo[iiState].Images.Height div 2; + if ImageInfo[iiCheck].Index > -1 then + ImageInfo[iiCheck].YPos := CellRect.Top + VAlign - ImageInfo[iiCheck].Images.Height div 2; +end; + + +//----------------- TCustomVirtualTreeOptions -------------------------------------------------------------------------- + +constructor TCustomVirtualTreeOptions.Create(AOwner : TCustomControl); +begin + FOwner := AOwner; + + FPaintOptions := DefaultPaintOptions; + FAnimationOptions := DefaultAnimationOptions; + FAutoOptions := DefaultAutoOptions; + FSelectionOptions := DefaultSelectionOptions; + FMiscOptions := DefaultMiscOptions; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCustomVirtualTreeOptions.SetAnimationOptions(const Value : TVTAnimationOptions); +begin + FAnimationOptions := Value; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCustomVirtualTreeOptions.SetAutoOptions(const Value : TVTAutoOptions); +var + ChangedOptions : TVTAutoOptions; +begin + if FAutoOptions <> Value then + begin + // Exclusive ORing to get all entries wich are in either set but not in both. + ChangedOptions := FAutoOptions + Value - (FAutoOptions * Value); + FAutoOptions := Value; + with FOwner do + if (toAutoSpanColumns in ChangedOptions) and not (csLoading in ComponentState) and HandleAllocated then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCustomVirtualTreeOptions.InternalSetMiscOptions(const Value : TVTMiscOptions); +begin + FMiscOptions := Value; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCustomVirtualTreeOptions.SetMiscOptions(const Value : TVTMiscOptions); +var + ToBeSet, ToBeCleared : TVTMiscOptions; +begin + if FMiscOptions <> Value then + begin + ToBeSet := Value - FMiscOptions; + ToBeCleared := FMiscOptions - Value; + FMiscOptions := Value; + + with TVTCracker(FOwner) do + if not (csLoading in ComponentState) and HandleAllocated then + begin + if toCheckSupport in ToBeSet + ToBeCleared then + Invalidate; + if toEditOnDblClick in ToBeSet then + FMiscOptions := FMiscOptions - [toToggleOnDblClick]; + // In order for toEditOnDblClick to take effect, we need to remove toToggleOnDblClick which is handled with priority. See issue #747 + + if not (csDesigning in ComponentState) then + begin + if toFullRepaintOnResize in ToBeSet + ToBeCleared then + RecreateWnd; + if toVariableNodeHeight in ToBeSet then + begin + BeginUpdate(); + try + ReInitNode(nil, True); + finally + EndUpdate(); + end; //try..finally + end; //if toVariableNodeHeight + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCustomVirtualTreeOptions.SetPaintOptions(const Value : TVTPaintOptions); +var + ToBeSet, ToBeCleared : TVTPaintOptions; + Run : PVirtualNode; + HandleWasAllocated : Boolean; +begin + if FPaintOptions <> Value then + begin + ToBeSet := Value - FPaintOptions; + ToBeCleared := FPaintOptions - Value; + FPaintOptions := Value; + if (toFixedIndent in ToBeSet) then + begin + // Fixes issue #388 + Include(FPaintOptions, toShowRoot); + Include(ToBeSet, toShowRoot); + end; //if + with TVTCracker(FOwner) do + begin + HandleWasAllocated := HandleAllocated; + + if not (csLoading in ComponentState) and (toShowFilteredNodes in ToBeSet + ToBeCleared) then + begin + if HandleWasAllocated then + BeginUpdate; + InterruptValidation; + Run := GetFirstNoInit; + while Assigned(Run) do + begin + if (vsFiltered in Run.States) then + begin + if FullyVisible[Run] then + begin + if toShowFilteredNodes in ToBeSet then + IncVisibleCount + else + DecVisibleCount; + end; + if toShowFilteredNodes in ToBeSet then + AdjustTotalHeight(Run, Run.NodeHeight, True) + else + AdjustTotalHeight(Run, - Run.NodeHeight, True); + end; + Run := GetNextNoInit(Run); + end; + if HandleWasAllocated then + EndUpdate; + end; + + if HandleAllocated then + begin + if ((tsUseThemes in TreeStates) or ((toThemeAware in ToBeSet) and StyleServices.Enabled)) and (toUseExplorerTheme in (ToBeSet + ToBeCleared)) and + not VclStyleEnabled then + begin + if (toUseExplorerTheme in ToBeSet) then + begin + SetWindowTheme('explorer'); + DoStateChange([tsUseExplorerTheme]); + end + else if toUseExplorerTheme in ToBeCleared then + begin + SetWindowTheme(''); + DoStateChange([], [tsUseExplorerTheme]); + end; + end; + + if not (csLoading in ComponentState) then + begin + if ((toThemeAware in ToBeSet + ToBeCleared) or (toUseExplorerTheme in ToBeSet + ToBeCleared) or VclStyleEnabled) then + begin + if ((toThemeAware in ToBeSet) and StyleServices.Enabled) then + DoStateChange([tsUseThemes]) + else if (toThemeAware in ToBeCleared) then + DoStateChange([], [tsUseThemes]); + + PrepareBitmaps(True, False); + RedrawWindow(nil, 0, RDW_INVALIDATE or RDW_VALIDATE or RDW_FRAME); + end; + + if toChildrenAbove in ToBeSet + ToBeCleared then + begin + InvalidateCache; + if UpdateCount = 0 then + begin + ValidateCache; + Invalidate; + end; + end; + + Invalidate; + end; + end; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCustomVirtualTreeOptions.SetSelectionOptions(const Value : TVTSelectionOptions); +var + ToBeSet, ToBeCleared : TVTSelectionOptions; +begin + if FSelectionOptions <> Value then + begin + ToBeSet := Value - FSelectionOptions; + ToBeCleared := FSelectionOptions - Value; + FSelectionOptions := Value; + + with TVTCracker(FOwner) do + begin + if (toMultiSelect in (ToBeCleared + ToBeSet)) or ([toLevelSelectConstraint, toSiblingSelectConstraint] * ToBeSet <> []) then + ClearSelection; + + if (toExtendedFocus in ToBeCleared) and (FocusedColumn > 0) and HandleAllocated then + begin + FocusedColumn := Header.MainColumn; + Invalidate; + end; + + if not (toExtendedFocus in FSelectionOptions) then + FocusedColumn := Header.MainColumn; + end; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCustomVirtualTreeOptions.StyleServices(AControl : TControl) : TCustomStyleServices; +begin + Result := VTStyleServices(FOwner); +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCustomVirtualTreeOptions.AssignTo(Dest : TPersistent); +begin + if Dest is TCustomVirtualTreeOptions then + begin + with Dest as TCustomVirtualTreeOptions do + begin + PaintOptions := Self.PaintOptions; + AnimationOptions := Self.AnimationOptions; + AutoOptions := Self.AutoOptions; + SelectionOptions := Self.SelectionOptions; + MiscOptions := Self.MiscOptions; + end; + end + else + inherited; +end; + +//----------------- TCustomStringTreeOptions --------------------------------------------------------------------------- + +constructor TCustomStringTreeOptions.Create(AOwner : TCustomControl); +begin + inherited; + FStringOptions := DefaultStringOptions; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCustomStringTreeOptions.SetStringOptions(const Value : TVTStringOptions); +var + ChangedOptions : TVTStringOptions; +begin + if FStringOptions <> Value then + begin + // Exclusive ORing to get all entries wich are in either set but not in both. + ChangedOptions := FStringOptions + Value - (FStringOptions * Value); + FStringOptions := Value; + with FOwner do + if (toShowStaticText in ChangedOptions) and not (csLoading in ComponentState) and HandleAllocated then + Invalidate; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TCustomStringTreeOptions.AssignTo(Dest : TPersistent); +begin + if Dest is TCustomStringTreeOptions then + begin + with Dest as TCustomStringTreeOptions do + begin + StringOptions := Self.StringOptions; + EditOptions := Self.EditOptions; + end; + end; + + // Let ancestors assign their options to the destination class. + inherited; +end; + +//----------------- TScrollBarOptions ---------------------------------------------------------------------------------- + +constructor TScrollBarOptions.Create(AOwner : TCustomControl); +begin + inherited Create; + + FOwner := AOwner; + FAlwaysVisible := False; + FScrollBarStyle := sbmRegular; + FScrollBars := TScrollStyle.ssBoth; + FIncrementX := 20; + FIncrementY := 20; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TScrollBarOptions.SetAlwaysVisible(Value : Boolean); +begin + if FAlwaysVisible <> Value then + begin + FAlwaysVisible := Value; + if not (csLoading in FOwner.ComponentState) and FOwner.HandleAllocated then + TVTCracker(FOwner).RecreateWnd; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TScrollBarOptions.SetScrollBars(Value : TScrollStyle); +begin + if FScrollBars <> Value then + begin + FScrollBars := Value; + if not (csLoading in FOwner.ComponentState) and FOwner.HandleAllocated then + TVTCracker(FOwner).RecreateWnd; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TScrollBarOptions.SetScrollBarStyle(Value : TScrollBarStyle); + +begin + if FScrollBarStyle <> Value then + begin + FScrollBarStyle := Value; + end; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TScrollBarOptions.GetOwner : TPersistent; + +begin + Result := FOwner; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +procedure TScrollBarOptions.Assign(Source : TPersistent); + +begin + if Source is TScrollBarOptions then + begin + AlwaysVisible := TScrollBarOptions(Source).AlwaysVisible; + HorizontalIncrement := TScrollBarOptions(Source).HorizontalIncrement; + ScrollBars := TScrollBarOptions(Source).ScrollBars; + ScrollBarStyle := TScrollBarOptions(Source).ScrollBarStyle; + VerticalIncrement := TScrollBarOptions(Source).VerticalIncrement; + end + else + inherited; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +{ TCheckStateHelper } + +function TCheckStateHelper.IsDisabled: Boolean; +begin + Result := Self >= TCheckState.csUncheckedDisabled; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCheckStateHelper.IsChecked: Boolean; +begin + Result := Self in [csCheckedNormal, csCheckedPressed, csCheckedDisabled]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCheckStateHelper.IsUnChecked: Boolean; +begin + Result := Self in [csUncheckedNormal, csUncheckedPressed, csUncheckedDisabled]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCheckStateHelper.IsMixed: Boolean; +begin + Result := Self in [csMixedNormal, csMixedPressed, csMixedDisabled]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCheckStateHelper.GetEnabled: TCheckState; +begin + Result := cEnabledState[Self]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCheckStateHelper.GetPressed(): TCheckState; +begin + Result := cPressedState[Self]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCheckStateHelper.GetUnpressed(): TCheckState; +begin + Result := cUnpressedState[Self]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function TCheckStateHelper.GetToggled(): TCheckState; +begin + Result := cToggledState[Self]; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +{ TSortDirectionHelper } + +function TSortDirectionHelper.ToInt() : Integer; +begin + Result := cSortDirectionToInt[Self]; +end; + + +end. diff --git a/components/virtualtreeview/Source/VirtualTrees.Utils.pas b/components/virtualtreeview/Source/VirtualTrees.Utils.pas index 14bc9cae2..f93f2fe03 100644 --- a/components/virtualtreeview/Source/VirtualTrees.Utils.pas +++ b/components/virtualtreeview/Source/VirtualTrees.Utils.pas @@ -34,11 +34,14 @@ interface System.Types, Vcl.Graphics, Vcl.ImgList, - Vcl.Controls; + Vcl.Controls, + VirtualTrees.Types; type - // Describes the mode how to blend pixels. + /// + /// Describes the mode how to blend pixels. + /// TBlendMode = ( bmConstantAlpha, // apply given constant alpha bmPerPixelAlpha, // use alpha value of the source pixel @@ -55,45 +58,89 @@ procedure SetBrushOrigin(Canvas: TCanvas; X, Y: Integer); inline; procedure SetCanvasOrigin(Canvas: TCanvas; X, Y: Integer); inline; -// Clip a given canvas to ClipRect while transforming the given rect to device coordinates. +/// +/// Clip a given canvas to ClipRect while transforming the given rect to device coordinates. +/// procedure ClipCanvas(Canvas: TCanvas; ClipRect: TRect; VisibleRegion: HRGN = 0); procedure DrawImage(ImageList: TCustomImageList; Index: Integer; Canvas: TCanvas; X, Y: Integer; Style: Cardinal; Enabled: Boolean); - -// Adjusts the given string S so that it fits into the given width. EllipsisWidth gives the width of -// the three points to be added to the shorted string. If this value is 0 then it will be determined implicitely. -// For higher speed (and multiple entries to be shorted) specify this value explicitely. -function ShortenString(DC: HDC; const S: string; Width: Integer; EllipsisWidth: Integer = 0): string; - -// Wrap the given string S so that it fits into a space of given width. -// RTL determines if right-to-left reading is active. +/// +/// Adjusts the given string S so that it fits into the given width. EllipsisWidth gives the width of +/// the three points to be added to the shorted string. If this value is 0 then it will be determined implicitely. +/// For higher speed (and multiple entries to be shorted) specify this value explicitely. +/// +function ShortenString(DC: HDC; const S: string; Width: TDimension; EllipsisWidth: TDimension = 0): string; overload; + +//-------------------------- +// ShortenString similar to VTV's version, except: +// -- Does not assume using three dots or any particular character for ellipsis +// -- Does not add ellipsis to string, so could be added anywhere +// -- Requires EllipsisWidth, and zero does nothing special +// Returns: +// ShortenedString as var param +// True if shortened (ie: add ellipsis somewhere), otherwise false +function ShortenString(TargetCanvasDC: HDC; const StrIn: string; const AllowedWidth_px: Integer; const EllipsisWidth_px: Integer; var ShortenedString: string): boolean; overload; + +/// +/// Wrap the given string S so that it fits into a space of given width. +/// RTL determines if right-to-left reading is active. +/// function WrapString(DC: HDC; const S: string; const Bounds: TRect; RTL: Boolean; DrawFormat: Cardinal): string; -// Calculates bounds of a drawing rectangle for the given string +/// +/// Calculates bounds of a drawing rectangle for the given string +/// procedure GetStringDrawRect(DC: HDC; const S: string; var Bounds: TRect; DrawFormat: Cardinal); -// Converts the incoming rectangle so that left and top are always less than or equal to right and bottom. +/// +/// Converts the incoming rectangle so that left and top are always less than or equal to right and bottom. +/// function OrderRect(const R: TRect): TRect; -// Fills the given rectangles with values which can be used while dragging around an image -// (used in DragMove of the drag manager and DragTo of the header columns). +/// +/// Fills the given rectangles with values which can be used while dragging around an image +/// +/// +/// (used in DragMove of the drag manager and DragTo of the header columns). +/// procedure FillDragRectangles(DragWidth, DragHeight, DeltaX, DeltaY: Integer; var RClip, RScroll, RSamp1, RSamp2, RDraw1, RDraw2: TRect); -// Attaches a bitmap as drag image to an IDataObject, see issue #405 -// Usage: Set property DragImageKind to diNoImage, in your event handler OnCreateDataObject -// call VirtualTrees.Utils.ApplyDragImage() with your `IDataObject` and your bitmap. +/// +/// Attaches a bitmap as drag image to an IDataObject, see issue #405 +/// +/// Usage: Set property DragImageKind to diNoImage, in your event handler OnCreateDataObject +/// call VirtualTrees.Utils.ApplyDragImage() with your `IDataObject` and your bitmap. +/// +/// procedure ApplyDragImage(const pDataObject: IDataObject; pBitmap: TBitmap); +/// /// Returns True if the mouse cursor is currently visible and False in case it is suppressed. /// Useful when doing hot-tracking on touchscreens, see issue #766 +/// function IsMouseCursorVisible(): Boolean; procedure ScaleImageList(const ImgList: TImageList; M, D: Integer); +/// /// Returns True if the high contrast theme is anabled in the system settings, False otherwise. +/// function IsHighContrastEnabled(): Boolean; +/// +/// Divide depend of parameter type uses different division operator: +/// Integer uses div +/// Single uses / +/// +function Divide(const Dimension: Integer; const DivideBy: Integer): Integer; overload; inline; + +/// +/// Divide depend of parameter type uses different division operator: +/// Integer uses div +/// Single uses / +/// +function Divide(const Dimension: Single; const DivideBy: Integer): Single; overload; inline; implementation @@ -233,13 +280,14 @@ procedure GetStringDrawRect(DC: HDC; const S: string; var Bounds: TRect; DrawFor //---------------------------------------------------------------------------------------------------------------------- -function ShortenString(DC: HDC; const S: string; Width: Integer; EllipsisWidth: Integer = 0): string; +function ShortenString(DC: HDC; const S: string; Width: TDimension; EllipsisWidth: TDimension = 0): string; var Size: TSize; Len: Integer; - L, H, N, W: Integer; - + L, H, N: Integer; + W: TDimension; + begin Len := Length(S); if (Len = 0) or (Width <= 0) then @@ -281,8 +329,75 @@ function ShortenString(DC: HDC; const S: string; Width: Integer; EllipsisWidth: end; end; + +//-------------------------- +function ShortenString(TargetCanvasDC: HDC; const StrIn: string; const AllowedWidth_px: Integer; const EllipsisWidth_px: Integer; var ShortenedString: string): boolean; +//-------------------------- +var + Size_px_x_px: TSize; // cx, cy + StrInLen: Integer; + LoLen, HiLen, TestLen, TestWidth_px: Integer; + +begin + StrInLen := Length(StrIn); + if (StrInLen = 0) then + Begin + ShortenedString := ''; + Result := False; // No ellipsis needed since original was empty + End else + if (AllowedWidth_px <= 0) then + Begin + ShortenedString := ''; + Result := True; // Ellipsis needed, since non-empty string replaced. + // But likely will get clipped if AllowedWidth is really zero + End else + begin + // Do a binary search for the optimal string length which fits into the given width. + LoLen := 0; + TestLen := 0; + TestWidth_px := AllowedWidth_px; + HiLen := StrInLen; + + while LoLen < HiLen do + begin + TestLen := (LoLen + HiLen + 1) shr 1; // Test average of Lo and Hi + + GetTextExtentPoint32W(TargetCanvasDC, PWideChar(StrIn), TestLen, Size_px_x_px); + TestWidth_px := Size_px_x_px.cx + EllipsisWidth_px; + + if TestWidth_px <= AllowedWidth_px then + Begin + LoLen := TestLen // Low bound must be at least as much as TestLen + End else + Begin + HiLen := TestLen - 1; // Continue until Hi bound string produces width below AllowedWidth_px + End; + end; + + if TestWidth_px <= AllowedWidth_px then + Begin + LoLen := TestLen; + End; + if LoLen >= StrInLen then + Begin + ShortenedString := StrIn; + Result := False; + End else if AllowedWidth_px <= EllipsisWidth_px then + Begin + ShortenedString := ''; + Result := True; // Even though Ellipsis won't fit in AllowedWidth, + // let clipping decide how much of ellipsis to show + End else + Begin + ShortenedString := Copy(StrIn, 1, LoLen); + Result := True; + End; + end; +end; + //---------------------------------------------------------------------------------------------------------------------- + function WrapString(DC: HDC; const S: string; const Bounds: TRect; RTL: Boolean; DrawFormat: Cardinal): string; var @@ -1264,27 +1379,8 @@ procedure FillDragRectangles(DragWidth, DragHeight, DeltaX, DeltaY: Integer; var TCustomImageListCast = class(TCustomImageList); procedure DrawImage(ImageList: TCustomImageList; Index: Integer; Canvas: TCanvas; X, Y: Integer; Style: Cardinal; Enabled: Boolean); - - procedure DrawDisabledImage(ImageList: TCustomImageList; Canvas: TCanvas; X, Y, Index: Integer); - var - Params: TImageListDrawParams; - begin - FillChar(Params, SizeOf(Params), 0); - Params.cbSize := SizeOf(Params); - Params.himl := ImageList.Handle; - Params.i := Index; - Params.hdcDst := Canvas.Handle; - Params.x := X; - Params.y := Y; - Params.fState := ILS_SATURATE; - ImageList_DrawIndirect(@Params); - end; - begin - if Enabled then - TCustomImageListCast(ImageList).DoDraw(Index, Canvas, X, Y, Style, Enabled) - else - DrawDisabledImage(ImageList, Canvas, X, Y, Index); + TCustomImageListCast(ImageList).DoDraw(Index, Canvas, X, Y, Style, Enabled) end; //---------------------------------------------------------------------------------------------------------------------- @@ -1367,6 +1463,8 @@ procedure ScaleImageList(const ImgList: TImageList; M, D: Integer); end; end; +//---------------------------------------------------------------------------------------------------------------------- + function IsHighContrastEnabled(): Boolean; var l: HIGHCONTRAST; @@ -1375,5 +1473,17 @@ function IsHighContrastEnabled(): Boolean; Result := SystemParametersInfo(SPI_GETHIGHCONTRAST, 0, @l, 0) and ((l.dwFlags and HCF_HIGHCONTRASTON) <> 0); end; +//---------------------------------------------------------------------------------------------------------------------- +function Divide(const Dimension: Single; const DivideBy: Integer): Single; +begin + Result:= Dimension / DivideBy; +end; + +//---------------------------------------------------------------------------------------------------------------------- + +function Divide(const Dimension: Integer; const DivideBy: Integer): Integer; +begin + Result:= Dimension div DivideBy; +end; end. diff --git a/components/virtualtreeview/Source/VirtualTrees.WorkerThread.pas b/components/virtualtreeview/Source/VirtualTrees.WorkerThread.pas index b58d0afbb..4e74d70ce 100644 --- a/components/virtualtreeview/Source/VirtualTrees.WorkerThread.pas +++ b/components/virtualtreeview/Source/VirtualTrees.WorkerThread.pas @@ -4,7 +4,8 @@ interface uses System.Classes, - VirtualTrees; + VirtualTrees.Types, + VirtualTrees.BaseTree; type // internal worker thread @@ -48,6 +49,7 @@ TBaseVirtualTreeCracker = class(TBaseVirtualTree) var WorkerThread: TWorkerThread = nil; + //----------------- TWorkerThread -------------------------------------------------------------------------------------- class procedure TWorkerThread.EnsureCreated(); @@ -57,6 +59,8 @@ class procedure TWorkerThread.EnsureCreated(); WorkerThread := TWorkerThread.Create(); end; +//---------------------------------------------------------------------------------------------------------------------- + class procedure TWorkerThread.Dispose(CanBlock: Boolean); var LRef: TThread; @@ -70,6 +74,7 @@ class procedure TWorkerThread.Dispose(CanBlock: Boolean); LRef.Free; end; +//---------------------------------------------------------------------------------------------------------------------- class procedure TWorkerThread.AddThreadReference; begin diff --git a/components/virtualtreeview/Source/VirtualTrees.dtx b/components/virtualtreeview/Source/VirtualTrees.dtx index acc249d9c..d014da365 100644 --- a/components/virtualtreeview/Source/VirtualTrees.dtx +++ b/components/virtualtreeview/Source/VirtualTrees.dtx @@ -105,13 +105,13 @@ If Reverse is True then a right-to-left column is being drawn, hence horizontal X and Y describe the left upper corner of the line image rectangle, while H denotes its height (and width). @@TBaseVirtualTree.FAlignment - + @@TBaseVirtualTree.FAnimationDuration - + @@TBaseVirtualTree.FAnimationType - + @@TBaseVirtualTree.FAutoExpandDelay amount of milliseconds to wait until a node is expanded if it is the @@ -821,7 +821,7 @@ true size of the font @@TEnumFormatEtc - + @@ -908,7 +908,7 @@ Do nothing. Let the application paint the background. @@TScrollBarOptions - + @@TScrollBarOptions.FScrollBars used to hide or show vertical and/or horizontal scrollbar @@ -1129,7 +1129,7 @@ Description If this column is allowed to be clicked then it is also kept for later use. Summary -Determines the column from the given position and returns it. +Determines the column from the given position and returns it. @@TVirtualTreeColumns.AdjustHoverColumn@TPoint Summary @@ -2055,13 +2055,13 @@ Version 4.0.0 For full document history see help file. Credits for their valuable assistance and code donations go to: - Freddy Ertl, Marian Aldenhvel, Thomas Bogenrieder, Jim Kuenemann, Werner Lehmann, Jens Treichler, - Paul Gallagher (IBO tree), Ondrej Kelle, Ronaldo Melo Ferraz, Heri Bender, Roland Bedrftig (BCB) + Freddy Ertl, Marian Aldenh�vel, Thomas Bogenrieder, Jim Kuenemann, Werner Lehmann, Jens Treichler, + Paul Gallagher (IBO tree), Ondrej Kelle, Ronaldo Melo Ferraz, Heri Bender, Roland Bed�rftig (BCB) Anthony Mills, Alexander Egorushkin (BCB), Mathias Torell (BCB), Frank van den Bergh, Vadim Sedulin, Peter Evans, Milan Vandrovec (BCB), Steve Moss (system check images), Joe White, David Clark (local node memory manager), Anders Thomsen, Igor Afanasyev, Eugene Programmer Beta testers: - Freddy Ertl, Hans-Jrgen Schnorrenberg, Werner Lehmann, Jim Kueneman, Vadim Sedulin, Moritz Franckenstein, + Freddy Ertl, Hans-J�rgen Schnorrenberg, Werner Lehmann, Jim Kueneman, Vadim Sedulin, Moritz Franckenstein, Wim van der Vegt, Franc v/d Westelaken Indirect contribution (via publicly accessible work of those persons): Alex Denissov, Hiroyuki Hori (MMXAsm expert) @@ -2084,7 +2084,7 @@ Version 4.0.0 @@Check button image indices - + @@ckButtonHot @@ -4712,7 +4712,7 @@ written again and silently disappear. Another task of this method is to work around the problem that TCollection is not streamed correctly when using Visual -Form Inheritance (VFI). +Form Inheritance (VFI). @@TBaseVirtualTree.Destroy Summary @@ -5687,7 +5687,7 @@ Occasionally you may want to shorten the node text at a different position, for and not the last folder or filename should be cut off but rather some mid level folders if possible. In the handler S must be processed (shortened) and returned in Result. If Done is set to true (default value is false) -the tree view takes over the shortening. This is useful if not all nodes or columns need +the tree view takes over the shortening. This is useful if not all nodes or columns need @@TCustomVirtualStringTree.AdjustPaintCellRect@TVTPaintInfo@TColumnIndex Summary @@ -5732,7 +5732,7 @@ Summary Options related to automatic actions. Description -These options can be used to switch certain actions in a tree which happen automatically under certain circumstances. +These options can be used to switch certain actions in a tree which happen automatically under certain circumstances. @@TCustomVirtualTreeOptions.MiscOptions Summary @@ -5753,7 +5753,7 @@ Summary Options related to painting. Description -These options can be used to switch visual aspects of a tree. +These options can be used to switch visual aspects of a tree. @@TCustomVirtualTreeOptions.SelectionOptions Summary @@ -6456,14 +6456,15 @@ a method resolution clause to avoid a name clash. The clause can look similar to procedure EditLinkSetBounds(R: TRect); stdcall; procedure IVTEditLink.SetBounds = EditLinkSetBounds; - + @@TCheckImageKind.ckSystemDefault System defined check images. - @@THeaderState.hsResizing + +@@THeaderState.hsResizing multi column resizing in progress - + @@THeaderState.hsColumnWidthTrackPending left button is down, user might want to start resize a column - + @@THeaderState.hsColumnWidthTracking column resizing is in progress diff --git a/components/virtualtreeview/Source/VirtualTrees.pas b/components/virtualtreeview/Source/VirtualTrees.pas index 6163db7b6..7a658f64e 100644 --- a/components/virtualtreeview/Source/VirtualTrees.pas +++ b/components/virtualtreeview/Source/VirtualTrees.pas @@ -22,15 +22,13 @@ // (C) 1999-2001 digital publishing AG. All Rights Reserved. //---------------------------------------------------------------------------------------------------------------------- // -// For a list of recent changes please see file CHANGES.TXT -// // Credits for their valuable assistance and code donations go to: // Freddy Ertl, Marian Aldenhoevel, Thomas Bogenrieder, Jim Kuenemann, Werner Lehmann, Jens Treichler, // Paul Gallagher (IBO tree), Ondrej Kelle, Ronaldo Melo Ferraz, Heri Bender, Roland Beduerftig (BCB) // Anthony Mills, Alexander Egorushkin (BCB), Mathias Torell (BCB), Frank van den Bergh, Vadim Sedulin, Peter Evans, // Milan Vandrovec (BCB), Steve Moss, Joe White, David Clark, Anders Thomsen, Igor Afanasyev, Eugene Programmer, // Corbin Dunn, Richard Pringle, Uli Gerhardt, Azza, Igor Savkic, Daniel Bauten, Timo Tegtmeier, Dmitry Zegebart, -// Andreas Hausladen, Joachim Marder, Roman Kassebaum, Vincent Parret, Dietmar Roesler, Sanjay Kanade, +// Andreas Hausladen, Joachim Marder, Roman Kassebaum, Vincent Parrett, Dietmar Roesler, Sanjay Kanade, // and everyone that sent pull requests: https://github.com/Virtual-TreeView/Virtual-TreeView/pulls?q= // Beta testers: // Freddy Ertl, Hans-Juergen Schnorrenberg, Werner Lehmann, Jim Kueneman, Vadim Sedulin, Moritz Franckenstein, @@ -62,3572 +60,377 @@ interface {$LEGACYIFEND ON} {$WARN UNSUPPORTED_CONSTRUCT OFF} -{$HPPEMIT '#include '} -{$HPPEMIT '#include '} -{$HPPEMIT '#include '} -{$ifdef BCB} - {$HPPEMIT '#pragma comment(lib, "VirtualTreesCR")'} -{$else} - {$HPPEMIT '#pragma comment(lib, "VirtualTreesR")'} -{$endif} -{$HPPEMIT '#pragma comment(lib, "Shell32")'} -{$HPPEMIT '#pragma comment(lib, "uxtheme")'} -{$HPPEMIT '#pragma link "VirtualTrees.Accessibility"'} - uses - Winapi.Windows, Winapi.oleacc, Winapi.Messages, System.SysUtils, Vcl.Graphics, - Vcl.Controls, Vcl.Forms, Vcl.ImgList, Winapi.ActiveX, Vcl.StdCtrls, System.Classes, - Vcl.Menus, Vcl.Printers, System.Types, Winapi.CommCtrl, Vcl.Themes, Winapi.UxTheme, - Winapi.ShlObj, System.UITypes, System.Generics.Collections, VirtualTrees.Types; -type + Winapi.Windows, Winapi.Messages, Winapi.ActiveX, + System.Classes, System.SysUtils, + Vcl.Graphics, Vcl.Controls, Vcl.ImgList, Vcl.Menus, Vcl.Themes, + VirtualTrees.Types, + VirtualTrees.Header, + VirtualTrees.BaseTree, {$IFDEF VT_FMX} - TDimension = Single; + VirtualTrees.AncestorFMX, {$ELSE} - TDimension = Integer; // For Firemonkey support, see #841 + VirtualTrees.AncestorVCL {$ENDIF} - -const - VTVersion = '7.6.2' deprecated 'This const is going to be removed in a future version'; - -const - VTTreeStreamVersion = 3; - VTHeaderStreamVersion = 6; // The header needs an own stream version to indicate changes only relevant to the header. - - CacheThreshold = 2000; // Number of nodes a tree must at least have to start caching and at the same - // time the maximum number of nodes between two cache entries. - FadeAnimationStepCount = 255; // Number of animation steps for hint fading (0..255). - ShadowSize = 5; // Size in pixels of the hint shadow. This value has no influence on Win2K and XP systems - // as those OSes have native shadow support. - cDefaultTextMargin = 4; // The default margin of text - - // Special identifiers for columns. - NoColumn = -1; - InvalidColumn = -2; - - // Indices for check state images used for checking. - ckEmpty = 0; // an empty image used as place holder - // radio buttons - ckRadioUncheckedNormal = 1; - ckRadioUncheckedHot = 2; - ckRadioUncheckedPressed = 3; - ckRadioUncheckedDisabled = 4; - ckRadioCheckedNormal = 5; - ckRadioCheckedHot = 6; - ckRadioCheckedPressed = 7; - ckRadioCheckedDisabled = 8; - // check boxes - ckCheckUncheckedNormal = 9; - ckCheckUncheckedHot = 10; - ckCheckUncheckedPressed = 11; - ckCheckUncheckedDisabled = 12; - ckCheckCheckedNormal = 13; - ckCheckCheckedHot = 14; - ckCheckCheckedPressed = 15; - ckCheckCheckedDisabled = 16; - ckCheckMixedNormal = 17; - ckCheckMixedHot = 18; - ckCheckMixedPressed = 19; - ckCheckMixedDisabled = 20; - // simple button - ckButtonNormal = 21; - ckButtonHot = 22; - ckButtonPressed = 23; - ckButtonDisabled = 24; - - // Instead using a TTimer class for each of the various events I use Windows timers with messages - // as this is more economical. - ExpandTimer = 1; - EditTimer = 2; - HeaderTimer = 3; - ScrollTimer = 4; - ChangeTimer = 5; - StructureChangeTimer = 6; - SearchTimer = 7; - ThemeChangedTimer = 8; - - ThemeChangedTimerDelay = 500; - - // Virtual Treeview does not need to be subclassed by an eventual Theme Manager instance as it handles - // Windows XP theme painting itself. Hence the special message is used to prevent subclassing. - CM_DENYSUBCLASSING = CM_BASE + 2000; - - // Decoupling message for auto-adjusting the internal edit window. - CM_AUTOADJUST = CM_BASE + 2005; - - // VT's own clipboard formats, - // Note: The reference format is used internally to allow to link to a tree reference - // to implement optimized moves and other back references. - CFSTR_VIRTUALTREE = 'Virtual Tree Data'; - CFSTR_VTREFERENCE = 'Virtual Tree Reference'; - CFSTR_HTML = 'HTML Format'; - CFSTR_RTF = 'Rich Text Format'; - CFSTR_RTFNOOBJS = 'Rich Text Format Without Objects'; - CFSTR_CSV = 'CSV'; - - // Drag image helpers for Windows 2000 and up. - IID_IDropTargetHelper: TGUID = (D1: $4657278B; D2: $411B; D3: $11D2; D4: ($83, $9A, $00, $C0, $4F, $D9, $18, $D0)); - IID_IDragSourceHelper: TGUID = (D1: $DE5BF786; D2: $477A; D3: $11D2; D4: ($83, $9D, $00, $C0, $4F, $D9, $18, $D0)); - IID_IDropTarget: TGUID = (D1: $00000122; D2: $0000; D3: $0000; D4: ($C0, $00, $00, $00, $00, $00, $00, $46)); - - // Help identifiers for exceptions. Application developers are responsible to link them with actual help topics. - hcTFEditLinkIsNil = 2000; - hcTFWrongMoveError = 2001; - hcTFWrongStreamFormat = 2002; - hcTFWrongStreamVersion = 2003; - hcTFStreamTooSmall = 2004; - hcTFCorruptStream1 = 2005; - hcTFCorruptStream2 = 2006; - hcTFClipboardFailed = 2007; - hcTFCannotSetUserData = 2008; - - // Header standard split cursor. - crHeaderSplit = TCursor(63); - - // Height changing cursor. - crVertSplit = TCursor(62); - -var // Clipboard format IDs used in OLE drag'n drop and clipboard transfers. - CF_VIRTUALTREE, - CF_VTREFERENCE, - CF_VRTF, - CF_VRTFNOOBJS, // Unfortunately CF_RTF* is already defined as being - // registration strings so I have to use different identifiers. - CF_HTML, - CF_CSV: Word; - - IsWinVistaOrAbove: Boolean; + ; + {$MinEnumSize 1, make enumerations as small as possible} - - type - // Alias defintions for convenience - TImageIndex = System.UITypes.TImageIndex; - TCanvas = Vcl.Graphics.TCanvas; - - - // The exception used by the trees. - EVirtualTreeError = class(Exception); - - PCardinal = ^Cardinal; - - // Limits the speed interval which can be used for auto scrolling (milliseconds). - TAutoScrollInterval = 1..1000; - - // Be careful when adding new states as this might change the size of the type which in turn - // changes the alignment in the node record as well as the stream chunks. - // Do not reorder the states and always add new states at the end of this enumeration in order to avoid - // breaking existing code. - TVirtualNodeState = ( - vsInitialized, // Set after the node has been initialized. - vsChecking, // Node's check state is changing, avoid propagation. - vsCutOrCopy, // Node is selected as cut or copy and paste source. - vsDisabled, // Set if node is disabled. - vsDeleting, // Set when the node is about to be freed. - vsExpanded, // Set if the node is expanded. - vsHasChildren, // Indicates the presence of child nodes without actually setting them. - vsVisible, // Indicate whether the node is visible or not (independant of the expand states of its parents). - vsSelected, // Set if the node is in the current selection. - vsOnFreeNodeCallRequired, // Set if user data has been set which requires OnFreeNode. - vsAllChildrenHidden, // Set if vsHasChildren is set and no child node has the vsVisible flag set. - vsReleaseCallOnUserDataRequired, // Indicates that the user data is a reference to an interface which should be released. - vsMultiline, // Node text is wrapped at the cell boundaries instead of being shorted. - vsHeightMeasured, // Node height has been determined and does not need a recalculation. - vsToggling, // Set when a node is expanded/collapsed to prevent recursive calls. - vsFiltered, // Indicates that the node should not be painted (without effecting its children). - vsInitializing // Set when the node is being initialized - ); - TVirtualNodeStates = set of TVirtualNodeState; - - // States used in InitNode to indicate states a node shall initially have. - TVirtualNodeInitState = ( - ivsDisabled, - ivsExpanded, - ivsHasChildren, - ivsMultiline, - ivsSelected, - ivsFiltered, - ivsReInit - ); - TVirtualNodeInitStates = set of TVirtualNodeInitState; - - TScrollBarStyle = ( - sbmRegular, - sbm3D - ); - - // Options per column. - TVTColumnOption = ( - coAllowClick, // Column can be clicked (must be enabled too). - coDraggable, // Column can be dragged. - coEnabled, // Column is enabled. - coParentBidiMode, // Column uses the parent's bidi mode. - coParentColor, // Column uses the parent's background color. - coResizable, // Column can be resized. - coShowDropMark, // Column shows the drop mark if it is currently the drop target. - coVisible, // Column is shown. - coAutoSpring, // Column takes part in the auto spring feature of the header (must be resizable too). - coFixed, // Column is fixed and can not be selected or scrolled etc. - coSmartResize, // Column is resized to its largest entry which is in view (instead of its largest - // visible entry). - coAllowFocus, // Column can be focused. - coDisableAnimatedResize, // Column resizing is not animated. - coWrapCaption, // Caption could be wrapped across several header lines to fit columns width. - coUseCaptionAlignment, // Column's caption has its own aligment. - coEditable, // Column can be edited - coStyleColor // Prefer background color of VCL style over TVirtualTreeColumn.Color - ); - TVTColumnOptions = set of TVTColumnOption; - - // These flags are used to indicate where a click in the header happened. - TVTHeaderHitPosition = ( - hhiNoWhere, // No column is involved (possible only if the tree is smaller than the client area). - hhiOnColumn, // On a column. - hhiOnIcon, // On the bitmap associated with a column. - hhiOnCheckbox // On the checkbox if enabled. - ); - TVTHeaderHitPositions = set of TVTHeaderHitPosition; - - // These flags are returned by the hit test method. - THitPosition = ( - hiAbove, // above the client area (if relative) or the absolute tree area - hiBelow, // below the client area (if relative) or the absolute tree area - hiNowhere, // no node is involved (possible only if the tree is not as tall as the client area) - hiOnItem, // on the bitmaps/buttons or label associated with an item - hiOnItemButton, // on the button associated with an item - hiOnItemButtonExact, // exactly on the button associated with an item - hiOnItemCheckbox, // on the checkbox if enabled - hiOnItemIndent, // in the indentation area in front of a node - hiOnItemLabel, // on the normal text area associated with an item - hiOnItemLeft, // in the area to the left of a node's text area (e.g. when right aligned or centered) - hiOnItemRight, // in the area to the right of a node's text area (e.g. if left aligned or centered) - hiOnNormalIcon, // on the "normal" image - hiOnStateIcon, // on the state image - hiToLeft, // to the left of the client area (if relative) or the absolute tree area - hiToRight, // to the right of the client area (if relative) or the absolute tree area - hiUpperSplitter, // in the upper splitter area of a node - hiLowerSplitter // in the lower splitter area of a node - ); - THitPositions = set of THitPosition; - - TCheckType = ( - ctNone, - ctTriStateCheckBox, - ctCheckBox, - ctRadioButton, - ctButton - ); - - // The check states include both, transient and fluent (temporary) states. The only temporary state defined so - // far is the pressed state. - TCheckState = ( - csUncheckedNormal, // unchecked and not pressed - csUncheckedPressed, // unchecked and pressed - csCheckedNormal, // checked and not pressed - csCheckedPressed, // checked and pressed - csMixedNormal, // 3-state check box and not pressed - csMixedPressed, // 3-state check box and pressed - csUncheckedDisabled,// disabled checkbox, not checkable - csCheckedDisabled, // disabled checkbox, not uncheckable - csMixedDisabled // disabled 3-state checkbox - ); - - /// Adds some convenience methods to type TCheckState - TCheckStateHelper = record helper for TCheckState - strict private - const - // Lookup to quickly convert a specific check state into its pressed counterpart and vice versa. - cPressedState: array[TCheckState] of TCheckState = ( - csUncheckedPressed, csUncheckedPressed, csCheckedPressed, csCheckedPressed, csMixedPressed, csMixedPressed, csUncheckedDisabled, csCheckedDisabled, csMixedDisabled); - cUnpressedState: array[TCheckState] of TCheckState = ( - csUncheckedNormal, csUncheckedNormal, csCheckedNormal, csCheckedNormal, csMixedNormal, csMixedNormal, csUncheckedDisabled, csCheckedDisabled, csMixedDisabled); - cEnabledState: array[TCheckState] of TCheckState = ( - csUncheckedNormal, csUncheckedPressed, csCheckedNormal, csCheckedPressed, csMixedNormal, csMixedPressed, csUncheckedNormal, csCheckedNormal, csMixedNormal); - cToggledState: array[TCheckState] of TCheckState = ( - csCheckedNormal, csCheckedPressed, csUnCheckedNormal, csUnCheckedPressed, csCheckedNormal, csCheckedPressed, csUncheckedDisabled, csCheckedDisabled, csMixedDisabled); - public - function GetPressed(): TCheckState; inline; - function GetUnpressed(): TCheckState; inline; - function GetEnabled(): TCheckState; inline; - function GetToggled(): TCheckState; inline; - function IsDisabled(): Boolean; inline; - function IsChecked(): Boolean; inline; - function IsUnChecked(): Boolean; inline; - function IsMixed(): Boolean; inline; - end; - - TCheckImageKind = ( - ckCustom, // application defined check images - ckSystemDefault // Uses the system check images, theme aware. - ); - - // mode to describe a move action - TVTNodeAttachMode = ( - amNoWhere, // just for simplified tests, means to ignore the Add/Insert command - amInsertBefore, // insert node just before destination (as sibling of destination) - amInsertAfter, // insert node just after destionation (as sibling of destination) - amAddChildFirst, // add node as first child of destination - amAddChildLast // add node as last child of destination - ); - - // modes to determine drop position further - TDropMode = ( - dmNowhere, - dmAbove, - dmOnNode, - dmBelow - ); - - // operations basically allowed during drag'n drop - TDragOperation = ( - doCopy, - doMove, - doLink - ); - TDragOperations = set of TDragOperation; - - TVTImageKind = ( - ikNormal, - ikSelected, - ikState, - ikOverlay - ); - - { - Fine points: Observed when fixing issue #623 - -- hmHint allows multiline hints automatically if provided through OnGetHint event. - This is irresptive of whether node itself is multi-line or not. - - -- hmToolTip shows a hint only when node text is not fully shown. It's meant to - fully show node text when not visible. It will show multi-line hint only if - the node itself is multi-line. If you provide a custom multi-line hint then - you must force linebreak style to hlbForceMultiLine in the OnGetHint event - in order to show the complete hint. - } - TVTHintMode = ( - hmDefault, // show the hint of the control - hmHint, // show node specific hint string returned by the application - hmHintAndDefault, // same as hmHint but show the control's hint if no node is concerned - hmTooltip // show the text of the node if it isn't already fully shown - ); - - // Indicates how to format a tooltip. - TVTTooltipLineBreakStyle = ( - hlbDefault, // Use multi-line style of the node. - hlbForceSingleLine, // Use single line hint. - hlbForceMultiLine // Use multi line hint. - ); - - TMouseButtons = set of TMouseButton; - - // Used to describe the action to do when using the OnBeforeItemErase event. - TItemEraseAction = ( - eaColor, // Use the provided color to erase the background instead the one of the tree. - eaDefault, // The tree should erase the item's background (bitmap or solid). - eaNone // Do nothing. Let the application paint the background. - ); - - - // There is a heap of switchable behavior in the tree. Since published properties may never exceed 4 bytes, - // which limits sets to at most 32 members, and because for better overview tree options are splitted - // in various sub-options and are held in a commom options class. - // - // Options to customize tree appearance: - TVTPaintOption = ( - toHideFocusRect, // Avoid drawing the dotted rectangle around the currently focused node. - toHideSelection, // Selected nodes are drawn as unselected nodes if the tree is unfocused. - toHotTrack, // Track which node is under the mouse cursor. - toPopupMode, // Paint tree as would it always have the focus (useful for tree combo boxes etc.) - toShowBackground, // Use the background image if there's one. - toShowButtons, // Display collapse/expand buttons left to a node. - toShowDropmark, // Show the dropmark during drag'n drop operations. - toShowHorzGridLines, // Display horizontal lines to simulate a grid. - toShowRoot, // Show lines also at top level (does not show the hidden/internal root node). - toShowTreeLines, // Display tree lines to show hierarchy of nodes. - toShowVertGridLines, // Display vertical lines (depending on columns) to simulate a grid. - toThemeAware, // Draw UI elements (header, tree buttons etc.) according to the current theme if - // enabled (Windows XP+ only, application must be themed). - toUseBlendedImages, // Enable alpha blending for ghosted nodes or those which are being cut/copied. - toGhostedIfUnfocused, // Ghosted images are still shown as ghosted if unfocused (otherwise the become non-ghosted - // images). - toFullVertGridLines, // Display vertical lines over the full client area, not only the space occupied by nodes. - // This option only has an effect if toShowVertGridLines is enabled too. - toAlwaysHideSelection, // Do not draw node selection, regardless of focused state. - toUseBlendedSelection, // Enable alpha blending for node selections. - toStaticBackground, // Show simple static background instead of a tiled one. - toChildrenAbove, // Display child nodes above their parent. - toFixedIndent, // Draw the tree with a fixed indent. - toUseExplorerTheme, // Use the explorer theme if run under Windows Vista (or above). - toHideTreeLinesIfThemed, // Do not show tree lines if theming is used. - toShowFilteredNodes // Draw nodes even if they are filtered out. - ); - TVTPaintOptions = set of TVTPaintOption; - - { Options to toggle animation support: - **Do not use toAnimatedToggle when a background image is used for the tree. - The animation does not look good as the image splits and moves with it. - } - TVTAnimationOption = ( - toAnimatedToggle, // Expanding and collapsing a node is animated (quick window scroll). - // **See note above. - toAdvancedAnimatedToggle // Do some advanced animation effects when toggling a node. - ); - TVTAnimationOptions = set of TVTAnimationOption; - - // Options which toggle automatic handling of certain situations: - TVTAutoOption = ( - toAutoDropExpand, // Expand node if it is the drop target for more than a certain time. - toAutoExpand, // Nodes are expanded (collapsed) when getting (losing) the focus. - toAutoScroll, // Scroll if mouse is near the border while dragging or selecting. - toAutoScrollOnExpand, // Scroll as many child nodes in view as possible after expanding a node. - toAutoSort, // Sort tree when Header.SortColumn or Header.SortDirection change or sort node if - // child nodes are added. Sorting will take place also if SortColum is NoColumn (-1). - toAutoSpanColumns, // Large entries continue into next column(s) if there's no text in them (no clipping). - toAutoTristateTracking, // Checkstates are automatically propagated for tri state check boxes. - toAutoHideButtons, // Node buttons are hidden when there are child nodes, but all are invisible. - toAutoDeleteMovedNodes, // Delete nodes which where moved in a drag operation (if not directed otherwise). - toDisableAutoscrollOnFocus, // Disable scrolling a node or column into view if it gets focused. - toAutoChangeScale, // Change default node height automatically if the system's font scale is set to big fonts. - toAutoFreeOnCollapse, // Frees any child node after a node has been collapsed (HasChildren flag stays there). - toDisableAutoscrollOnEdit, // Do not center a node horizontally when it is edited. - toAutoBidiColumnOrdering // When set then columns (if any exist) will be reordered from lowest index to highest index - // and vice versa when the tree's bidi mode is changed. - ); - TVTAutoOptions = set of TVTAutoOption; - - // Options which determine the tree's behavior when selecting nodes: - TVTSelectionOption = ( - toDisableDrawSelection, // Prevent user from selecting with the selection rectangle in multiselect mode. - toExtendedFocus, // Entries other than in the main column can be selected, edited etc. - toFullRowSelect, // Hit test as well as selection highlight are not constrained to the text of a node. - toLevelSelectConstraint, // Constrain selection to the same level as the selection anchor. - toMiddleClickSelect, // Allow selection, dragging etc. with the middle mouse button. This and toWheelPanning - // are mutual exclusive. - toMultiSelect, // Allow more than one node to be selected. - toRightClickSelect, // Allow selection, dragging etc. with the right mouse button. - toSiblingSelectConstraint, // Constrain selection to nodes with same parent. - toCenterScrollIntoView, // Center nodes vertically in the client area when scrolling into view. - toSimpleDrawSelection, // Simplifies draw selection, so a node's caption does not need to intersect with the - // selection rectangle. - toAlwaysSelectNode, // If this flag is set to true, the tree view tries to always have a node selected. - // This behavior is closer to the Windows TreeView and useful in Windows Explorer style applications. - toRestoreSelection, // Set to true if upon refill the previously selected nodes should be selected again. - // The nodes will be identified by its caption (text in MainColumn) - // You may use TVTHeader.RestoreSelectiuonColumnIndex to define an other column that should be used for indentification. - toSyncCheckboxesWithSelection // If checkboxes are shown, they follow the change in selections. When checkboxes are - // changed, the selections follow them and vice-versa. - // **Only supported for ctCheckBox type checkboxes. - ); - TVTSelectionOptions = set of TVTSelectionOption; - - TVTEditOptions = ( - toDefaultEdit, // Standard behaviour for end of editing (after VK_RETURN stay on edited cell). - toVerticalEdit, // After VK_RETURN switch to next column. - toHorizontalEdit // After VK_RETURN switch to next row. - ); - - // Options which do not fit into any of the other groups: - TVTMiscOption = ( - toAcceptOLEDrop, // Register tree as OLE accepting drop target - toCheckSupport, // Show checkboxes/radio buttons. - toEditable, // Node captions can be edited. - toFullRepaintOnResize, // Fully invalidate the tree when its window is resized (CS_HREDRAW/CS_VREDRAW). - toGridExtensions, // Use some special enhancements to simulate and support grid behavior. - toInitOnSave, // Initialize nodes when saving a tree to a stream. - toReportMode, // Tree behaves like TListView in report mode. - toToggleOnDblClick, // Toggle node expansion state when it is double clicked. - toWheelPanning, // Support for mouse panning (wheel mice only). This option and toMiddleClickSelect are - // mutal exclusive, where panning has precedence. - toReadOnly, // The tree does not allow to be modified in any way. No action is executed and - // node editing is not possible. - toVariableNodeHeight, // When set then GetNodeHeight will trigger OnMeasureItem to allow variable node heights. - toFullRowDrag, // Start node dragging by clicking anywhere in it instead only on the caption or image. - // Must be used together with toDisableDrawSelection. - toNodeHeightResize, // Allows changing a node's height via mouse. - toNodeHeightDblClickResize, // Allows to reset a node's height to FDefaultNodeHeight via a double click. - toEditOnClick, // Editing mode can be entered with a single click - toEditOnDblClick, // Editing mode can be entered with a double click - toReverseFullExpandHotKey // Used to define Ctrl+'+' instead of Ctrl+Shift+'+' for full expand (and similar for collapsing) - ); - TVTMiscOptions = set of TVTMiscOption; - - // Options to control data export - TVTExportMode = ( - emAll, // export all records (regardless checked state) - emChecked, // export checked records only - emUnchecked, // export unchecked records only - emVisibleDueToExpansion, //Do not export nodes that are not visible because their parent is not expanded - emSelected // export selected nodes only - ); - - // Kinds of operations - TVTOperationKind = ( - okAutoFitColumns, - okGetMaxColumnWidth, - okSortNode, - okSortTree, - okExport, - okExpand - ); - TVTOperationKinds = set of TVTOperationKind; - - // content elements of the control from left to right, used when calculatin left margins. - TVTElement = ( - ofsMargin, // right of the margin - ofsToggleButton, // the exact x-postition of the toggle button - ofsCheckBox, - ofsStateImage, - ofsImage, - ofsLabel, // where drawing a selection begins - ofsText, // includes TextMargin - ofsRightOfText, // Includes NodeWidth and ExtraNodeWidth - ofsEndOfClientArea // The end of the paint area - ); - - /// An array that can be used to calculate the offsets ofthe elements in the tree. - TVTOffsets = array [TVTElement] of TDimension; - - TAddPopupItemType = ( - apNormal, - apDisabled, - apHidden - ); + // Some aliases for backward compatiblity + PVirtualNode = VirtualTrees.Types.PVirtualNode; + TVirtualNode = VirtualTrees.Types.TVirtualNode; + TVTHeaderColumnLayout = VirtualTrees.Types.TVTHeaderColumnLayout; + TSmartAutoFitType = VirtualTrees.Types.TSmartAutoFitType; + TVirtualTreeStates = VirtualTrees.Types.TVirtualTreeStates; + TCheckState = VirtualTrees.Types.TCheckState; + TCheckType = VirtualTrees.Types.TCheckType; + TSortDirection = VirtualTrees.Types.TSortDirection; + TColumnIndex = VirtualTrees.Types.TColumnIndex; + TVTColumnOption = VirtualTrees.Types.TVTColumnOption; + TVTHeaderHitInfo = VirtualTrees.Types.TVTHeaderHitInfo; + TVTHeaderHitPosition = VirtualTrees.Types.TVTHeaderHitPosition; + TVTHeaderHitPositions = VirtualTrees.Types.TVTHeaderHitPositions; + THeaderState = VirtualTrees.Types.THeaderState; + THeaderStates = VirtualTrees.Types.THeaderStates; + TDropMode = VirtualTrees.Types.TDropMode; + TFormatArray = VirtualTrees.Types.TFormatArray; + TVTHeaderOption = VirtualTrees.Types.TVTHeaderOption; + TVTHeaderOptions = VirtualTrees.Types.TVTHeaderOptions; + TVTHeaderStyle = VirtualTrees.Types.TVTHeaderStyle; + TVTExportType = VirtualTrees.Types.TVTExportType; + TVTImageKind = VirtualTrees.Types.TVTImageKind; + TVTExportMode = VirtualTrees.Types.TVTExportMode; + TVTOperationKind = VirtualTrees.Types.TVTOperationKind; + TVTUpdateState = VirtualTrees.Types.TVTUpdateState; + TVTCellPaintMode = VirtualTrees.Types.TVTCellPaintMode; + TVirtualNodeState = VirtualTrees.Types.TVirtualNodeState; + TVirtualNodeInitState = VirtualTrees.Types.TVirtualNodeInitState; + TVirtualNodeInitStates = VirtualTrees.Types.TVirtualNodeInitStates; + TVTTooltipLineBreakStyle = VirtualTrees.Types.TVTTooltipLineBreakStyle; + TVTNodeAttachMode = VirtualTrees.Types.TVTNodeAttachMode; + TNodeArray = VirtualTrees.Types.TNodeArray; + THitInfo = VirtualTrees.Types.THitInfo; + THitPosition = VirtualTrees.Types.THitPosition; + TVTPaintOption = VirtualTrees.Types.TVTPaintOption; + TVTAutoOption = VirtualTrees.Types.TVTAutoOption; + TVTAutoOptions = VirtualTrees.Types.TVTAutoOptions; + TVTSelectionOption = VirtualTrees.Types.TVTSelectionOption; + TVSTTextType = VirtualTrees.Types.TVSTTextType; + TVTHintMode = VirtualTrees.Types.TVTHintMode; + TBaseVirtualTree = VirtualTrees.BaseTree.TBaseVirtualTree; + IVTEditLink = VirtualTrees.BaseTree.IVTEditLink; + TVTHeaderNotifyEvent = VirtualTrees.BaseTree.TVTHeaderNotifyEvent; + TVTCompareEvent = VirtualTrees.BaseTree.TVTCompareEvent; + TVirtualTreeColumn = VirtualTrees.Header.TVirtualTreeColumn; + TVirtualTreeColumns = VirtualTrees.Header.TVirtualTreeColumns; + TVTHeader = VirtualTrees.Header.TVTHeader; + TVTHeaderClass = VirtualTrees.Header.TVTHeaderClass; + THeaderPaintInfo = VirtualTrees.Header.THeaderPaintInfo; + TVTConstraintPercent = VirtualTrees.Header.TVTConstraintPercent; + TVTFixedAreaConstraints = VirtualTrees.Header.TVTFixedAreaConstraints; + TColumnsArray = VirtualTrees.Header.TColumnsArray; + TCanvas = Vcl.Graphics.TCanvas; const - DefaultPaintOptions = [toShowButtons, toShowDropmark, toShowTreeLines, toShowRoot, toThemeAware, toUseBlendedImages]; - DefaultAnimationOptions = []; - DefaultAutoOptions = [toAutoDropExpand, toAutoTristateTracking, toAutoScrollOnExpand, toAutoDeleteMovedNodes, toAutoChangeScale, toAutoSort]; - DefaultSelectionOptions = []; - DefaultMiscOptions = [toAcceptOLEDrop, toFullRepaintOnResize, toInitOnSave, toToggleOnDblClick, toWheelPanning, - toEditOnClick]; - DefaultColumnOptions = [coAllowClick, coDraggable, coEnabled, coParentColor, coParentBidiMode, coResizable, - coShowDropmark, coVisible, coAllowFocus, coEditable, coStyleColor]; + // Aliases for increased compatibility with V7, feel free to extend by pull requests + NoColumn = VirtualTrees.Types.NoColumn; + InvalidColumn = VirtualTrees.Types.InvalidColumn; + sdAscending = VirtualTrees.Types.TSortDirection.sdAscending; + sdDescending = VirtualTrees.Types.TSortDirection.sdDescending; + toAutoSort = VirtualTrees.Types.TVTAutoOption.toAutoSort; + toCheckSupport = VirtualTrees.Types.TVTMiscOption.toCheckSupport; + toEditable = VirtualTrees.Types.TVTMiscOption.toEditable; + toShowRoot = VirtualTrees.Types.TVTPaintOption.toShowRoot; + ctNone = VirtualTrees.Types.TCheckType.ctNone; + ctTriStateCheckBox = VirtualTrees.Types.TCheckType.ctTriStateCheckBox; + ctCheckBox = VirtualTrees.Types.TCheckType.ctCheckBox; + ctRadioButton = VirtualTrees.Types.TCheckType.ctRadioButton; + ctButton = VirtualTrees.Types.TCheckType.ctButton; + + csUncheckedNormal = VirtualTrees.Types.TCheckState.csUncheckedNormal; + csUncheckedPressed = VirtualTrees.Types.TCheckState.csUncheckedPressed; + csCheckedNormal = VirtualTrees.Types.TCheckState.csCheckedNormal; + csCheckedPressed = VirtualTrees.Types.TCheckState.csCheckedPressed; + csMixedNormal = VirtualTrees.Types.TCheckState.csMixedNormal; + csMixedPressed = VirtualTrees.Types.TCheckState.csMixedPressed; + csUncheckedDisabled = VirtualTrees.Types.TCheckState.csUncheckedDisabled; + csCheckedDisabled = VirtualTrees.Types.TCheckState.csCheckedDisabled; + csMixedDisable = VirtualTrees.Types.TCheckState.csMixedDisabled; + + coVisible = VirtualTrees.Types.TVTColumnOption.coVisible; + vsDisabled = VirtualTrees.Types.TVirtualNodeState.vsDisabled; + etHTML = VirtualTrees.Types.TVTExportType.etHTML; + hiOnItemButton = VirtualTrees.Types.THitPosition.hiOnItemButton; + dmOnNode = VirtualTrees.Types.TDropMode.dmOnNode; + hlbForceMultiLine = VirtualTrees.Types.TVTTooltipLineBreakStyle.hlbForceMultiLine; + hmHintAndDefault = VirtualTrees.Types.TVTHintMode.hmHintAndDefault; + hmTooltip = VirtualTrees.Types.TVTHintMode.hmTooltip; type - TBaseVirtualTree = class; - TVirtualTreeClass = class of TBaseVirtualTree; - - PVirtualNode = ^TVirtualNode; - - TColumnIndex = type Integer; - TColumnPosition = type Cardinal; - - // This record must already be defined here and not later because otherwise BCB users will not be able - // to compile (conversion done by BCB is wrong). - TCacheEntry = record - Node: PVirtualNode; - AbsoluteTop: Cardinal; - end; - - TCache = array of TCacheEntry; - TNodeArray = array of PVirtualNode; - - TCustomVirtualTreeOptions = class(TPersistent) - private - FOwner: TBaseVirtualTree; - FPaintOptions: TVTPaintOptions; - FAnimationOptions: TVTAnimationOptions; - FAutoOptions: TVTAutoOptions; - FSelectionOptions: TVTSelectionOptions; - FMiscOptions: TVTMiscOptions; - FExportMode: TVTExportMode; - FEditOptions: TVTEditOptions; - procedure SetAnimationOptions(const Value: TVTAnimationOptions); - procedure SetAutoOptions(const Value: TVTAutoOptions); - procedure SetMiscOptions(const Value: TVTMiscOptions); - procedure SetPaintOptions(const Value: TVTPaintOptions); - procedure SetSelectionOptions(const Value: TVTSelectionOptions); - protected - // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) - function StyleServices(AControl: TControl = nil): TCustomStyleServices; - //these bypass the side effects in the regular setters. - procedure InternalSetMiscOptions(const Value: TVTMiscOptions); - public - constructor Create(AOwner: TBaseVirtualTree); virtual; - procedure AssignTo(Dest: TPersistent); override; - property AnimationOptions: TVTAnimationOptions read FAnimationOptions write SetAnimationOptions default DefaultAnimationOptions; - property AutoOptions: TVTAutoOptions read FAutoOptions write SetAutoOptions default DefaultAutoOptions; - property ExportMode: TVTExportMode read FExportMode write FExportMode default emAll; - property MiscOptions: TVTMiscOptions read FMiscOptions write SetMiscOptions default DefaultMiscOptions; - property PaintOptions: TVTPaintOptions read FPaintOptions write SetPaintOptions default DefaultPaintOptions; - property SelectionOptions: TVTSelectionOptions read FSelectionOptions write SetSelectionOptions default DefaultSelectionOptions; - property EditOptions: TVTEditOptions read FEditOptions write FEditOptions default toDefaultEdit; - - property Owner: TBaseVirtualTree read FOwner; - end; - - TTreeOptionsClass = class of TCustomVirtualTreeOptions; - - TVirtualTreeOptions = class(TCustomVirtualTreeOptions) - published - property AnimationOptions; - property AutoOptions; - property ExportMode; - property MiscOptions; - property PaintOptions; - property SelectionOptions; - end; - - // Used in the CF_VTREFERENCE clipboard format. - PVTReference = ^TVTReference; - TVTReference = record - Process: Cardinal; - Tree: TBaseVirtualTree; - end; - - TVirtualNode = packed record - Index, // index of node with regard to its parent - ChildCount: Cardinal; // number of child nodes - NodeHeight: Word; // height in pixels - States: TVirtualNodeStates; // states describing various properties of the node (expanded, initialized etc.) - Align: Byte; // line/button alignment - CheckState: TCheckState; // indicates the current check state (e.g. checked, pressed etc.) - CheckType: TCheckType; // indicates which check type shall be used for this node - Dummy: Byte; // dummy value to fill DWORD boundary TODO: Is this still necessary? - TotalCount, // sum of this node, all of its child nodes and their child nodes etc. - TotalHeight: Cardinal; // height in pixels this node covers on screen including the height of all of its - // children - // Note: Some copy routines require that all pointers (as well as the data area) in a node are - // located at the end of the node! Hence if you want to add new member fields (except pointers to internal - // data) then put them before field Parent. - Parent, // reference to the node's parent (for the root this contains the treeview) - PrevSibling, // link to the node's previous sibling or nil if it is the first node - NextSibling, // link to the node's next sibling or nil if it is the last node - FirstChild, // link to the node's first child... - LastChild: PVirtualNode; // link to the node's last child... - private - Data: record end; // this is a placeholder, each node gets extra data determined by NodeDataSize - public - function IsAssigned(): Boolean; inline; - function GetData(): Pointer; overload; inline; - function GetData(): T; overload; inline; - procedure SetData(pUserData: Pointer); overload; - procedure SetData(pUserData: T); overload; - procedure SetData(const pUserData: IInterface); overload; - end; - - - // Structure used when info about a certain position in the header is needed. - TVTHeaderHitInfo = record - X, - Y: TDimension; - Button: TMouseButton; - Shift: TShiftState; - Column: TColumnIndex; - HitPosition: TVTHeaderHitPositions; - end; + TCustomVirtualStringTree = class; - // Structure used when info about a certain position in the tree is needed. - THitInfo = record - HitNode: PVirtualNode; - HitPositions: THitPositions; - HitColumn: TColumnIndex; - HitPoint: TPoint; - end; +{$IFDEF VT_FMX} + TVTAncestor = TVTAncestorFMX; +{$ELSE} + TVTAncestor = TVTAncestorVcl; +{$ENDIF} - // auto scroll directions - TScrollDirections = set of ( - sdLeft, - sdUp, - sdRight, - sdDown + // Describes the source to use when converting a string tree into a string for clipboard etc. + TVSTTextSourceType = ( + tstAll, // All nodes are rendered. Initialization is done on the fly. + tstInitialized, // Only initialized nodes are rendered. + tstSelected, // Only selected nodes are rendered. + tstCutCopySet, // Only nodes currently marked as being in the cut/copy clipboard set are rendered. + tstVisible, // Only visible nodes are rendered. + tstChecked // Only checked nodes are rendered ); - // OLE drag'n drop support - TFormatEtcArray = array of TFormatEtc; - TFormatArray = array of Word; - - // IDataObject.SetData support - TInternalStgMedium = packed record - Format: TClipFormat; - Medium: TStgMedium; - end; - TInternalStgMediumArray = array of TInternalStgMedium; - - TEnumFormatEtc = class(TInterfacedObject, IEnumFormatEtc) - private - FTree: TBaseVirtualTree; - FFormatEtcArray: TFormatEtcArray; - FCurrentIndex: Integer; - public - constructor Create(Tree: TBaseVirtualTree; const AFormatEtcArray: TFormatEtcArray); - - function Clone(out Enum: IEnumFormatEtc): HResult; stdcall; - function Next(celt: Integer; out elt; pceltFetched: PLongint): HResult; stdcall; - function Reset: HResult; stdcall; - function Skip(celt: Integer): HResult; stdcall; - end; - - // ----- OLE drag'n drop handling - - IVTDragManager = interface(IUnknown) - ['{C4B25559-14DA-446B-8901-0C879000EB16}'] - procedure ForceDragLeave; stdcall; - function GetDataObject: IDataObject; stdcall; - function GetDragSource: TBaseVirtualTree; stdcall; - function GetDropTargetHelperSupported: Boolean; stdcall; - function GetIsDropTarget: Boolean; stdcall; - - property DataObject: IDataObject read GetDataObject; - property DragSource: TBaseVirtualTree read GetDragSource; - property DropTargetHelperSupported: Boolean read GetDropTargetHelperSupported; - property IsDropTarget: Boolean read GetIsDropTarget; - end; - - // This data object is used in two different places. One is for clipboard operations and the other while dragging. - TVTDataObject = class(TInterfacedObject, IDataObject) - private - FOwner: TBaseVirtualTree; // The tree which provides clipboard or drag data. - FForClipboard: Boolean; // Determines which data to render with GetData. - FFormatEtcArray: TFormatEtcArray; - FInternalStgMediumArray: TInternalStgMediumArray; // The available formats in the DataObject - FAdviseHolder: IDataAdviseHolder; // Reference to an OLE supplied implementation for advising. - protected - function CanonicalIUnknown(const TestUnknown: IUnknown): IUnknown; - function EqualFormatEtc(FormatEtc1, FormatEtc2: TFormatEtc): Boolean; - function FindFormatEtc(TestFormatEtc: TFormatEtc; const FormatEtcArray: TFormatEtcArray): integer; - function FindInternalStgMedium(Format: TClipFormat): PStgMedium; - function HGlobalClone(HGlobal: THandle): THandle; - function RenderInternalOLEData(const FormatEtcIn: TFormatEtc; var Medium: TStgMedium; var OLEResult: HResult): Boolean; - function StgMediumIncRef(const InStgMedium: TStgMedium; var OutStgMedium: TStgMedium; - CopyInMedium: Boolean; const DataObject: IDataObject): HRESULT; - - property ForClipboard: Boolean read FForClipboard; - property FormatEtcArray: TFormatEtcArray read FFormatEtcArray write FFormatEtcArray; - property InternalStgMediumArray: TInternalStgMediumArray read FInternalStgMediumArray write FInternalStgMediumArray; - property Owner: TBaseVirtualTree read FOwner; - public - constructor Create(AOwner: TBaseVirtualTree; ForClipboard: Boolean); virtual; - destructor Destroy; override; - - function DAdvise(const FormatEtc: TFormatEtc; advf: Integer; const advSink: IAdviseSink; out dwConnection: Integer): - HResult; virtual; stdcall; - function DUnadvise(dwConnection: Integer): HResult; virtual; stdcall; - function EnumDAdvise(out enumAdvise: IEnumStatData): HResult; virtual; stdcall; - function EnumFormatEtc(Direction: Integer; out EnumFormatEtc: IEnumFormatEtc): HResult; virtual; stdcall; - function GetCanonicalFormatEtc(const FormatEtc: TFormatEtc; out FormatEtcOut: TFormatEtc): HResult; virtual; stdcall; - function GetData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium): HResult; virtual; stdcall; - function GetDataHere(const FormatEtc: TFormatEtc; out Medium: TStgMedium): HResult; virtual; stdcall; - function QueryGetData(const FormatEtc: TFormatEtc): HResult; virtual; stdcall; - function SetData(const FormatEtc: TFormatEtc; var Medium: TStgMedium; DoRelease: BOOL): HResult; virtual; stdcall; - end; - - // TVTDragManager is a class to manage drag and drop in a Virtual Treeview. - TVTDragManager = class(TInterfacedObject, IVTDragManager, IDropSource, IDropTarget) - private - FOwner, // The tree which is responsible for drag management. - FDragSource: TBaseVirtualTree; // Reference to the source tree if the source was a VT, might be different than - // the owner tree. - FIsDropTarget: Boolean; // True if the owner is currently the drop target. - FDataObject: IDataObject; // A reference to the data object passed in by DragEnter (only used when the owner - // tree is the current drop target). - FDropTargetHelper: IDropTargetHelper; // Win2k > Drag image support - FFullDragging: BOOL; // True, if full dragging is currently enabled in the system. - - function GetDataObject: IDataObject; stdcall; - function GetDragSource: TBaseVirtualTree; stdcall; - function GetDropTargetHelperSupported: Boolean; stdcall; - function GetIsDropTarget: Boolean; stdcall; - public - constructor Create(AOwner: TBaseVirtualTree); virtual; - destructor Destroy; override; - - function DragEnter(const DataObject: IDataObject; KeyState: Integer; Pt: TPoint; - var Effect: Longint): HResult; stdcall; - function DragLeave: HResult; stdcall; - function DragOver(KeyState: Integer; Pt: TPoint; var Effect: LongInt): HResult; stdcall; - function Drop(const DataObject: IDataObject; KeyState: Integer; Pt: TPoint; var Effect: Integer): HResult; stdcall; - procedure ForceDragLeave; stdcall; - function GiveFeedback(Effect: Integer): HResult; stdcall; - function QueryContinueDrag(EscapePressed: BOOL; KeyState: Integer): HResult; stdcall; - end; + TVSTGetTextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; + TextType: TVSTTextType; var CellText: string) of object; + TVSTGetHintEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; + var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: string) of object; + // New text can only be set for variable caption. + TVSTNewTextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; + NewText: string) of object; + /// String tree event for custom handling of string abbreviations. + /// The instance that fired the event. + /// Teh canvas on that the sending control will paint. + /// The Node that is going to be painted. + /// The column index that is going to be painted. + /// Var parameter that contains the caption or string that should be used. + /// Boolean var paramter: Assign True if a string is passed in the Result parameter. Leave the default value False if no shorting is need or the control shuld do it. + /// + /// If the text of a node does not fit into its cell (in grid mode) or is too wide for the width of the tree view it is being abbreviated with an ellipsis (...). By default the ellipsis is added to the end of the node text. + /// Occasionally you may want to shorten the node text at a different position, for example if the node text is a path string and not the last folder or filename should be cut off but rather some mid level folders if possible. + /// + TVSTShortenStringEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; + Column: TColumnIndex; const S: string; TextSpace: TDimension; var Result: string; + var Done: Boolean) of object; + TVTMeasureTextEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; + Column: TColumnIndex; const Text: string; var Extent: TDimension) of object; + TVTDrawTextEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; + Column: TColumnIndex; const Text: string; const CellRect: TRect; var DefaultDraw: Boolean) of object; - PVTHintData = ^TVTHintData; - TVTHintData = record - Tree: TBaseVirtualTree; + /// Event arguments of the OnGetCellText event + TVSTGetCellTextEventArgs = record Node: PVirtualNode; Column: TColumnIndex; - HintRect: TRect; // used for draw trees only, string trees get the size from the hint string - HintText: string; // set when size of the hint window is calculated - BidiMode: TBidiMode; - Alignment: TAlignment; - LineBreakStyle: TVTToolTipLineBreakStyle; - end; - - // The trees need an own hint window class because of Unicode output and adjusted font. - TVirtualTreeHintWindow = class(THintWindow) - strict private - FHintData: TVTHintData; - FTextHeight: TDimension; - procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED; - procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND; - strict protected - procedure CreateParams(var Params: TCreateParams); override; - procedure Paint; override; - // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) - function StyleServices(AControl: TControl = nil): TCustomStyleServices; - public - function CalcHintRect(MaxWidth: TDimension; const AHint: string; AData: Pointer): TRect; override; - function IsHintMsg(var Msg: TMsg): Boolean; override; + CellText: string; + StaticText: string; + StaticTextAlignment: TAlignment; + ExportType: TVTExportType; + constructor Create(pNode: PVirtualNode; pColumn: TColumnIndex; pExportType: TVTExportType = TVTExportType.etNone); end; - // Drag image support for the tree. - TVTTransparency = 0..255; - TVTBias = -128..127; - - // Simple move limitation for the drag image. - TVTDragMoveRestriction = ( - dmrNone, - dmrHorizontalOnly, - dmrVerticalOnly - ); - - TVTDragImageStates = set of ( - disHidden, // Internal drag image is currently hidden (always hidden if drag image helper interfaces are used). - disInDrag, // Drag image class is currently being used. - disPrepared, // Drag image class is prepared. - disSystemSupport // Running on Windows 2000 or higher. System supports drag images natively. - ); + /// Event signature which is called when text is painted on the canvas or needed for the export. + TVSTGetCellTextEvent = procedure(Sender: TCustomVirtualStringTree; var E: TVSTGetCellTextEventArgs) of object; - // Class to manage header and tree drag image during a drag'n drop operation. - TVTDragImage = class + TCustomVirtualStringTree = class(TVTAncestor) private - FOwner: TBaseVirtualTree; - FBackImage, // backup of overwritten screen area - FAlphaImage, // target for alpha blending - FDragImage: TBitmap; // the actual drag image to blend to screen - FImagePosition, // position of image (upper left corner) in screen coordinates - FLastPosition: TPoint; // last mouse position in screen coordinates - FTransparency: TVTTransparency; // alpha value of the drag image (0 - fully transparent, 255 - fully opaque) - FPreBlendBias, // value to darken or lighten the drag image before it is blended - FPostBlendBias: TVTBias; // value to darken or lighten the alpha blend result - FFade: Boolean; // determines whether to fade the drag image from center to borders or not - FRestriction: TVTDragMoveRestriction; // determines in which directions the drag image can be moved - FColorKey: TColor; // color to make fully transparent regardless of any other setting - FStates: TVTDragImageStates; // Determines the states of the drag image class. - function GetVisible: Boolean; // True if the drag image is currently hidden (used only when dragging) - procedure InternalShowDragImage(ScreenDC: HDC); - procedure MakeAlphaChannel(Source, Target: TBitmap); - procedure RecaptureBackground(Tree: TBaseVirtualTree; R: TRect; VisibleRegion: HRGN; CaptureNCArea, - ReshowDragImage: Boolean); - function WillMove(P: TPoint): Boolean; - property Visible: Boolean read GetVisible; - property PreBlendBias: TVTBias read FPreBlendBias write FPreBlendBias default 0; - property Transparency: TVTTransparency read FTransparency write FTransparency default 128; - property ColorKey: TColor read FColorKey write FColorKey default clWindow; - property Fade: Boolean read FFade write FFade default False; - public - constructor Create(AOwner: TBaseVirtualTree); - destructor Destroy; override; - - function DragTo(P: TPoint; ForceRepaint: Boolean): Boolean; - procedure EndDrag; - function GetDragImageRect: TRect; - procedure HideDragImage; - procedure PrepareDrag(DragImage: TBitmap; ImagePosition, HotSpot: TPoint; const DataObject: IDataObject); - procedure ShowDragImage; - property ImagePosition : TPoint read FImagePosition; - property LastPosition : TPoint read FLastPosition; - property MoveRestriction: TVTDragMoveRestriction read FRestriction write FRestriction default dmrNone; - end; - - // tree columns implementation - TVirtualTreeColumns = class; - TVTHeader = class; - - TVirtualTreeColumnStyle = ( - vsText, - vsOwnerDraw - ); + FInternalDataOffset: Cardinal; // offset to the internal data of the string tree + FDefaultText: string; // text to show if there's no OnGetText event handler (e.g. at design time) + FTextHeight: Integer; // true size of the font + FEllipsisWidth: Integer; // width of '...' for the current font - TVTHeaderColumnLayout = ( - blGlyphLeft, - blGlyphRight, - blGlyphTop, - blGlyphBottom - ); + FOnGetText: TVSTGetTextEvent; // used to retrieve the string to be displayed for a specific node + fOnGetCellText: TVSTGetCellTextEvent; // used to retrieve the normal and static text of a tree node + FOnGetHint: TVSTGetHintEvent; // used to retrieve the hint to be displayed for a specific node + FOnNewText: TVSTNewTextEvent; // used to notify the application about an edited node caption + FOnShortenString: TVSTShortenStringEvent; // used to allow the application a customized string shortage + FOnMeasureTextWidth: TVTMeasureTextEvent; // used to adjust the width of the cells + FOnMeasureTextHeight: TVTMeasureTextEvent; + FOnDrawText: TVTDrawTextEvent; // used to custom draw the node text + /// Returns True if the property DefaultText has a value that differs from the default value, False otherwise. + function IsDefaultTextStored(): Boolean; + function GetImageText(Node: PVirtualNode; Kind: TVTImageKind; + Column: TColumnIndex): string; + function GetOptions: TCustomStringTreeOptions; + function GetStaticText(Node: PVirtualNode; Column: TColumnIndex): string; + function GetText(Node: PVirtualNode; Column: TColumnIndex): string; + procedure ReadText(Reader: TReader); + procedure WriteText(Writer: TWriter); + procedure ResetInternalData(Node: PVirtualNode; Recursive: Boolean); + procedure SetDefaultText(const Value: string); + procedure SetOptions(const Value: TCustomStringTreeOptions); + procedure SetText(Node: PVirtualNode; Column: TColumnIndex; const Value: string); + procedure WMSetFont(var Msg: TWMSetFont); message WM_SETFONT; + procedure GetDataFromGrid(const AStrings : TStringList; const IncludeHeading : Boolean = True); + protected + /// Contains the name of the string that should be restored as selection + /// + FPreviouslySelected: TStringList; + procedure InitializeTextProperties(var PaintInfo: TVTPaintInfo); + procedure PaintNormalText(var PaintInfo: TVTPaintInfo; TextOutFlags: Integer; Text: string); virtual; + procedure PaintStaticText(const PaintInfo: TVTPaintInfo; pStaticTextAlignment: TAlignment; const Text: string); virtual; // [IPK] - private to protected + procedure AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; var NextNonEmpty: TColumnIndex); override; + function CanExportNode(Node: PVirtualNode): Boolean; + function CalculateStaticTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: string): TDimension; virtual; + function CalculateTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: string): TDimension; virtual; + function ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean; override; + procedure DefineProperties(Filer: TFiler); override; + function DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; override; + procedure DoAddToSelection(Node: PVirtualNode); override; + function DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): string; override; + function DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): string; override; + function DoGetNodeExtraWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): TDimension; override; + function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): TDimension; override; + procedure DoGetText(var pEventArgs: TVSTGetCellTextEventArgs); virtual; + function DoIncrementalSearch(Node: PVirtualNode; const Text: string): Integer; override; + procedure DoNewText(Node: PVirtualNode; Column: TColumnIndex; const Text: string); virtual; + procedure DoPaintNode(var PaintInfo: TVTPaintInfo); override; + function DoShortenString(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const S: string; Width: TDimension; + EllipsisWidth: TDimension = 0): string; virtual; + procedure DoTextDrawing(var PaintInfo: TVTPaintInfo; const Text: string; CellRect: TRect; DrawFormat: Cardinal); virtual; + function DoTextMeasuring(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: string): TSize; virtual; + function GetOptionsClass: TTreeOptionsClass; override; + procedure GetRenderStartValues(Source: TVSTTextSourceType; var Node: PVirtualNode; + var NextNodeProc: TGetNextNodeProc); + function InternalData(Node: PVirtualNode): Pointer; + procedure MainColumnChanged; override; + function ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType, + ChunkSize: Integer): Boolean; override; + procedure ReadOldStringOptions(Reader: TReader); + function RenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; ForClipboard: Boolean): HResult; override; + procedure SetChildCount(Node: PVirtualNode; NewChildCount: Cardinal); override; + procedure WriteChunks(Stream: TStream; Node: PVirtualNode); override; - TSortDirection = ( - sdAscending, - sdDescending - ); + property DefaultText: string read FDefaultText write SetDefaultText stored False;// Stored via own writer + property EllipsisWidth: Integer read FEllipsisWidth; + property TreeOptions: TCustomStringTreeOptions read GetOptions write SetOptions; - TSortDirectionHelper = record helper for VirtualTrees.TSortDirection - strict private - const cSortDirectionToInt: Array [TSortDirection] of Integer = (1, -1); + property OnGetHint: TVSTGetHintEvent read FOnGetHint write FOnGetHint; + property OnGetText: TVSTGetTextEvent read FOnGetText write FOnGetText; + property OnGetCellText: TVSTGetCellTextEvent read fOnGetCellText write fOnGetCellText; + property OnNewText: TVSTNewTextEvent read FOnNewText write FOnNewText; + property OnShortenString: TVSTShortenStringEvent read FOnShortenString write FOnShortenString; + property OnMeasureTextWidth: TVTMeasureTextEvent read FOnMeasureTextWidth write FOnMeasureTextWidth; + property OnMeasureTextHeight: TVTMeasureTextEvent read FOnMeasureTextHeight write FOnMeasureTextHeight; + property OnDrawText: TVTDrawTextEvent read FOnDrawText write FOnDrawText; public - /// Returns +1 for ascending and -1 for descending sort order. - function ToInt(): Integer; inline; - end; - - // Used during owner draw of the header to indicate which drop mark for the column must be drawn. - TVTDropMarkMode = ( - dmmNone, - dmmLeft, - dmmRight - ); - - TVirtualTreeColumn = class; - - // This structure carries all important information about header painting and is used in the advanced header painting. - THeaderPaintInfo = record - TargetCanvas: TCanvas; - Column: TVirtualTreeColumn; - PaintRectangle: TRect; - TextRectangle: TRect; - IsHoverIndex, - IsDownIndex, - IsEnabled, - ShowHeaderGlyph, - ShowSortGlyph, - ShowRightBorder: Boolean; - DropMark: TVTDropMarkMode; - GlyphPos, - SortGlyphPos: TPoint; - SortGlyphSize: TSize; - procedure DrawSortArrow(pDirection: TSortDirection); - procedure DrawDropMark(); + constructor Create(AOwner: TComponent); override; + destructor Destroy(); override; + function AddChild(Parent: PVirtualNode; UserData: Pointer = nil): PVirtualNode; override; + function ComputeNodeHeight(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; S: string = ''): TDimension; virtual; + function ContentToClipboard(Format: Word; Source: TVSTTextSourceType): HGLOBAL; + procedure ContentToCustom(Source: TVSTTextSourceType); + function ContentToHTML(Source: TVSTTextSourceType; const Caption: string = ''): String; + function ContentToRTF(Source: TVSTTextSourceType): RawByteString; + function ContentToText(Source: TVSTTextSourceType; Separator: Char): String; overload; + function ContentToUnicode(Source: TVSTTextSourceType; Separator: WideChar): string; overload; deprecated 'Use ContentToText instead'; + function ContentToText(Source: TVSTTextSourceType; const Separator: string): string; overload; + procedure GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect; + var Text: string); override; + function InvalidateNode(Node: PVirtualNode): TRect; override; + function Path(Node: PVirtualNode; Column: TColumnIndex; Delimiter: Char): string; + procedure ReinitNode(Node: PVirtualNode; Recursive: Boolean; ForceReinit: + Boolean = False); override; + procedure RemoveFromSelection(Node: PVirtualNode); override; + function SaveToCSVFile(const FileNameWithPath : TFileName; const IncludeHeading : Boolean) : Boolean; + /// Alternate text for images used in Accessibility. + property ImageText[Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex]: string read GetImageText; + property StaticText[Node: PVirtualNode; Column: TColumnIndex]: string read GetStaticText; + property Text[Node: PVirtualNode; Column: TColumnIndex]: string read GetText write SetText; end; - - TVirtualTreeColumn = class(TCollectionItem) - private - const cDefaultColumnSpacing = 3; + {$if CompilerVersion >= 33} + [ComponentPlatformsAttribute(pfidWindows)] + {$ifend} + TVirtualStringTree = class(TCustomVirtualStringTree) private - FText, - FHint: string; - FWidth: TDimension; - FPosition: TColumnPosition; - FMinWidth: TDimension; - FMaxWidth: TDimension; - FStyle: TVirtualTreeColumnStyle; - FImageIndex: TImageIndex; - FBiDiMode: TBiDiMode; - FLayout: TVTHeaderColumnLayout; - FMargin, - FSpacing: TDimension; - FOptions: TVTColumnOptions; - FEditOptions: TVTEditOptions; - FEditNextColumn: TDimension; - FTag: NativeInt; - FAlignment: TAlignment; - FCaptionAlignment: TAlignment; // Alignment of the caption. - FLastWidth: TDimension; - FColor: TColor; - FBonusPixel: Boolean; - FSpringRest: Single; // Accumulator for width adjustment when auto spring option is enabled. - FCaptionText: string; - FCheckBox: Boolean; - FCheckType: TCheckType; - FCheckState: TCheckState; - FImageRect: TRect; - FHasImage: Boolean; - FDefaultSortDirection: TSortDirection; - function GetCaptionAlignment: TAlignment; - function GetCaptionWidth: TDimension; - function GetLeft: TDimension; - function IsBiDiModeStored: Boolean; - function IsCaptionAlignmentStored: Boolean; - function IsColorStored: Boolean; - procedure SetAlignment(const Value: TAlignment); - procedure SetBiDiMode(Value: TBiDiMode); - procedure SetCaptionAlignment(const Value: TAlignment); - procedure SetCheckBox(Value: Boolean); - procedure SetCheckState(Value: TCheckState); - procedure SetCheckType(Value: TCheckType); - procedure SetColor(const Value: TColor); - procedure SetImageIndex(Value: TImageIndex); - procedure SetLayout(Value: TVTHeaderColumnLayout); - procedure SetMargin(Value: TDimension); - procedure SetMaxWidth(Value: TDimension); - procedure SetMinWidth(Value: TDimension); - procedure SetOptions(Value: TVTColumnOptions); - procedure SetPosition(Value: TColumnPosition); - procedure SetSpacing(Value: TDimension); - procedure SetStyle(Value: TVirtualTreeColumnStyle); - procedure SetWidth(Value: TDimension); + function GetOptions: TStringTreeOptions; + procedure SetOptions(const Value: TStringTreeOptions); protected - FLeft: TDimension; - procedure ChangeScale(M, D: TDimension; isDpiChange: Boolean); virtual; - procedure ComputeHeaderLayout(var PaintInfo: THeaderPaintInfo; DrawFormat: Cardinal; CalculateTextRect: Boolean = False); - procedure DefineProperties(Filer: TFiler); override; - procedure GetAbsoluteBounds(var Left, Right: TDimension); - function GetDisplayName: string; override; - function GetText: string; virtual; // [IPK] - procedure SetText(const Value: string); virtual; // [IPK] private to protected & virtual - function GetOwner: TVirtualTreeColumns; reintroduce; - procedure InternalSetWidth(const value : TDimension); //bypass side effects in SetWidth - procedure ReadHint(Reader: TReader); - procedure ReadText(Reader: TReader); - procedure SetCollection(Value: TCollection); override; + function GetOptionsClass: TTreeOptionsClass; override; public - constructor Create(Collection: TCollection); override; - destructor Destroy; override; - - procedure Assign(Source: TPersistent); override; - function Equals(OtherColumnObj: TObject): Boolean; override; - function GetRect: TRect; virtual; - property HasImage: Boolean read FHasImage; - property ImageRect: TRect read FImageRect; - procedure LoadFromStream(const Stream: TStream; Version: Integer); - procedure ParentBiDiModeChanged; - procedure ParentColorChanged; - procedure RestoreLastWidth; - function GetEffectiveColor(): TColor; - procedure SaveToStream(const Stream: TStream); - function UseRightToLeftReading: Boolean; - - property BonusPixel: Boolean read FBonusPixel write FBonusPixel; - property CaptionText: string read FCaptionText; - property Left: TDimension read GetLeft; - property Owner: TVirtualTreeColumns read GetOwner; - property SpringRest: Single read FSpringRest write FSpringRest; + property Canvas; + property RangeX; + property LastDragEffect; + property CheckImageKind; // should no more be published to make #622 fix working published - property Alignment: TAlignment read FAlignment write SetAlignment default taLeftJustify; - property BiDiMode: TBiDiMode read FBiDiMode write SetBiDiMode stored IsBiDiModeStored; - property CaptionAlignment: TAlignment read GetCaptionAlignment write SetCaptionAlignment - stored IsCaptionAlignmentStored default taLeftJustify; - property CaptionWidth: TDimension read GetCaptionWidth; - property CheckType: TCheckType read FCheckType write SetCheckType default ctCheckBox; - property CheckState: TCheckState read FCheckState write SetCheckState default csUncheckedNormal; - property CheckBox: Boolean read FCheckBox write SetCheckBox default False; - property Color: TColor read FColor write SetColor stored IsColorStored; - property DefaultSortDirection: TSortDirection read FDefaultSortDirection write FDefaultSortDirection default sdAscending; - property Hint: string read FHint write FHint; - property ImageIndex: TImageIndex read FImageIndex write SetImageIndex default -1; - property Layout: TVTHeaderColumnLayout read FLayout write SetLayout default blGlyphLeft; - property Margin: TDimension read FMargin write SetMargin default 4; - property MaxWidth: TDimension read FMaxWidth write SetMaxWidth default 10000; - property MinWidth: TDimension read FMinWidth write SetMinWidth default 10; - property Options: TVTColumnOptions read FOptions write SetOptions default DefaultColumnOptions; - property EditOptions: TVTEditOptions read FEditOptions write FEditOptions default toDefaultEdit; - property EditNextColumn: TDimension read FEditNextColumn write FEditNextColumn default -1; - property Position: TColumnPosition read FPosition write SetPosition; - property Spacing: TDimension read FSpacing write SetSpacing default cDefaultColumnSpacing; - property Style: TVirtualTreeColumnStyle read FStyle write SetStyle default vsText; - property Tag: NativeInt read FTag write FTag default 0; - property Text: string read GetText write SetText; - property Width: TDimension read FWidth write SetWidth default 50; - end; - - TVirtualTreeColumnClass = class of TVirtualTreeColumn; - - TColumnsArray = array of TVirtualTreeColumn; - TCardinalArray = array of Cardinal; - TIndexArray = array of TColumnIndex; - - TVirtualTreeColumns = class(TCollection) - private - FHeader: TVTHeader; - FHeaderBitmap: TBitmap; // backbuffer for drawing - FHoverIndex, // currently "hot" column - FDownIndex, // Column on which a mouse button is held down. - FTrackIndex: TColumnIndex; // Index of column which is currently being resized. - FClickIndex: TColumnIndex; // Index of the last clicked column. - FCheckBoxHit: Boolean; // True if the last click was on a header checkbox. - FPositionToIndex: TIndexArray; - FDefaultWidth: TDimension; // the width columns are created with - FNeedPositionsFix: Boolean; // True if FixPositions must still be called after DFM loading or Bidi mode change. - FClearing: Boolean; // True if columns are being deleted entirely. - FColumnPopupMenu: TPopupMenu; // Member for storing the TVTHeaderPopupMenu - - function GetCount: TDimension; - function GetItem(Index: TColumnIndex): TVirtualTreeColumn; - function GetNewIndex(P: TPoint; var OldIndex: TColumnIndex): Boolean; - procedure SetDefaultWidth(Value: TDimension); - procedure SetItem(Index: TColumnIndex; Value: TVirtualTreeColumn); - protected - // drag support - FDragIndex: TColumnIndex; // index of column currently being dragged - FDropTarget: TColumnIndex; // current target column (index) while dragging - FDropBefore: Boolean; // True if drop position is in the left half of a column, False for the right - // side to drop the dragged column to - - procedure AdjustAutoSize(CurrentIndex: TColumnIndex; Force: Boolean = False); - function AdjustDownColumn(P: TPoint): TColumnIndex; - function AdjustHoverColumn(P: TPoint): Boolean; - procedure AdjustPosition(Column: TVirtualTreeColumn; Position: Cardinal); - function CanSplitterResize(P: TPoint; Column: TColumnIndex): Boolean; - procedure DoCanSplitterResize(P: TPoint; Column: TColumnIndex; var Allowed: Boolean); virtual; - procedure DrawButtonText(DC: HDC; Caption: string; Bounds: TRect; Enabled, Hot: Boolean; DrawFormat: Cardinal; - WrapCaption: Boolean); - procedure FixPositions; - function GetColumnAndBounds(P: TPoint; var ColumnLeft, ColumnRight: TDimension; Relative: Boolean = True): Integer; - function GetOwner: TPersistent; override; - function HandleClick(P: TPoint; Button: TMouseButton; Force, DblClick: Boolean): Boolean; virtual; - procedure HeaderPopupMenuAddHeaderPopupItem(const Sender: TBaseVirtualTree; const Column: TColumnIndex; - var Cmd: TAddPopupItemType); - procedure HeaderPopupMenuColumnChange(const Sender: TBaseVirtualTree; const Column: TColumnIndex; Visible: Boolean); - procedure IndexChanged(OldIndex, NewIndex: Integer); - procedure InitializePositionArray; - procedure Notify(Item: TCollectionItem; Action: System.Classes.TCollectionNotification); override; - procedure ReorderColumns(RTL: Boolean); - procedure SetHoverIndex(index : TColumnIndex); - procedure Update(Item: TCollectionItem); override; - procedure UpdatePositions(Force: Boolean = False); - - property HeaderBitmap: TBitmap read FHeaderBitmap; - property PositionToIndex: TIndexArray read FPositionToIndex; - property HoverIndex: TColumnIndex read FHoverIndex write FHoverIndex; - property DownIndex: TColumnIndex read FDownIndex write FDownIndex; - property CheckBoxHit: Boolean read FCheckBoxHit write FCheckBoxHit; - // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) - function StyleServices(AControl: TControl = nil): TCustomStyleServices; - public - constructor Create(AOwner: TVTHeader); virtual; - destructor Destroy; override; - - function Add: TVirtualTreeColumn; virtual; - procedure AnimatedResize(Column: TColumnIndex; NewWidth: TDimension); - procedure Assign(Source: TPersistent); override; - procedure Clear; virtual; - function ColumnFromPosition(P: TPoint; Relative: Boolean = True): TColumnIndex; overload; virtual; - function ColumnFromPosition(PositionIndex: TColumnPosition): TColumnIndex; overload; virtual; - function Equals(OtherColumnsObj: TObject): Boolean; override; - procedure GetColumnBounds(Column: TColumnIndex; var Left, Right: TDimension); - function GetFirstVisibleColumn(ConsiderAllowFocus: Boolean = False): TColumnIndex; - function GetLastVisibleColumn(ConsiderAllowFocus: Boolean = False): TColumnIndex; - function GetFirstColumn: TColumnIndex; - function GetNextColumn(Column: TColumnIndex): TColumnIndex; - function GetNextVisibleColumn(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex; - function GetPreviousColumn(Column: TColumnIndex): TColumnIndex; - function GetPreviousVisibleColumn(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex; - function GetScrollWidth: TDimension; - function GetVisibleColumns: TColumnsArray; - function GetVisibleFixedWidth: TDimension; - function IsValidColumn(Column: TColumnIndex): Boolean; - procedure LoadFromStream(const Stream: TStream; Version: Integer); - procedure PaintHeader(DC: HDC; R: TRect; HOffset: TDimension); overload; virtual; - procedure PaintHeader(TargetCanvas: TCanvas; R: TRect; const Target: TPoint; - RTLOffset: TDimension = 0); overload; virtual; - procedure SaveToStream(const Stream: TStream); - procedure EndUpdate(); override; - function TotalWidth: TDimension; - - property Count: Integer read GetCount; - property ClickIndex: TColumnIndex read FClickIndex; - property DefaultWidth: TDimension read FDefaultWidth write SetDefaultWidth; - property DragIndex : TColumnIndex read FDragIndex write FDragIndex; - property DropBefore : boolean read FDropBefore write FDropBefore; - property DropTarget : TColumnIndex read FDropTarget write FDropTarget; - property Items[Index: TColumnIndex]: TVirtualTreeColumn read GetItem write SetItem; default; - property Header: TVTHeader read FHeader; - property TrackIndex: TColumnIndex read FTrackIndex write FTrackIndex; - end; - - TVirtualTreeColumnsClass = class of TVirtualTreeColumns; - - TVTConstraintPercent = 0..100; - TVTFixedAreaConstraints = class(TPersistent) - private - FHeader: TVTHeader; - FMaxHeightPercent, - FMaxWidthPercent, - FMinHeightPercent, - FMinWidthPercent: TVTConstraintPercent; - FOnChange: TNotifyEvent; - procedure SetConstraints(Index: Integer; Value: TVTConstraintPercent); - protected - procedure Change; - property Header: TVTHeader read FHeader; - public - constructor Create(AOwner: TVTHeader); - - procedure Assign(Source: TPersistent); override; - property OnChange: TNotifyEvent read FOnChange write FOnChange; - published - property MaxHeightPercent: TVTConstraintPercent index 0 read FMaxHeightPercent write SetConstraints default 0; - property MaxWidthPercent: TVTConstraintPercent index 1 read FMaxWidthPercent write SetConstraints default 95; - property MinHeightPercent: TVTConstraintPercent index 2 read FMinHeightPercent write SetConstraints default 0; - property MinWidthPercent: TVTConstraintPercent index 3 read FMinWidthPercent write SetConstraints default 0; - end; - - TVTHeaderStyle = ( - hsThickButtons, // TButton look and feel - hsFlatButtons, // flatter look than hsThickButton, like an always raised flat TToolButton - hsPlates // flat TToolButton look and feel (raise on hover etc.) - ); - - TVTHeaderOption = ( - hoAutoResize, // Adjust a column so that the header never exceeds the client width of the owner control. - hoColumnResize, // Resizing columns with the mouse is allowed. - hoDblClickResize, // Allows a column to resize itself to its largest entry. - hoDrag, // Dragging columns is allowed. - hoHotTrack, // Header captions are highlighted when mouse is over a particular column. - hoOwnerDraw, // Header items with the owner draw style can be drawn by the application via event. - hoRestrictDrag, // Header can only be dragged horizontally. - hoShowHint, // Show application defined header hint. - hoShowImages, // Show header images. - hoShowSortGlyphs, // Allow visible sort glyphs. - hoVisible, // Header is visible. - hoAutoSpring, // Distribute size changes of the header to all columns, which are sizable and have the - // coAutoSpring option enabled. - hoFullRepaintOnResize, // Fully invalidate the header (instead of subsequent columns only) when a column is resized. - hoDisableAnimatedResize, // Disable animated resize for all columns. - hoHeightResize, // Allow resizing header height via mouse. - hoHeightDblClickResize, // Allow the header to resize itself to its default height. - hoHeaderClickAutoSort, // Clicks on the header will make the clicked column the SortColumn or toggle sort direction if - // it already was the sort column - hoAutoColumnPopupMenu, // Show a context menu for activating and deactivating columns on right click - hoAutoResizeInclCaption // Includes the header caption for the auto resizing - ); - TVTHeaderOptions = set of TVTHeaderOption; - - THeaderState = ( - hsAutoSizing, // auto size chain is in progess, do not trigger again on WM_SIZE - hsDragging, // header dragging is in progress (only if enabled) - hsDragPending, // left button is down, user might want to start dragging a column - hsLoading, // The header currently loads from stream, so updates are not necessary. - hsColumnWidthTracking, // column resizing is in progress - hsColumnWidthTrackPending, // left button is down, user might want to start resize a column - hsHeightTracking, // height resizing is in progress - hsHeightTrackPending, // left button is down, user might want to start changing height - hsResizing, // multi column resizing in progress - hsScaling, // the header is scaled after a change of FixedAreaConstraints or client size - hsNeedScaling // the header needs to be scaled - ); - THeaderStates = set of THeaderState; - - - TSmartAutoFitType = ( - smaAllColumns, // consider nodes in view only for all columns - smaNoColumn, // consider nodes in view only for no column - smaUseColumnOption // use coSmartResize of the corresponding column - ); // describes the used column resize behaviour for AutoFitColumns - - - TChangeReason = ( - crIgnore, // used as placeholder - crAccumulated, // used for delayed changes - crChildAdded, // one or more child nodes have been added - crChildDeleted, // one or more child nodes have been deleted - crNodeAdded, // a node has been added - crNodeCopied, // a node has been duplicated - crNodeMoved // a node has been moved to a new place - ); // desribes what made a structure change event happen - - TVTHeader = class(TPersistent) - private - FOwner: TBaseVirtualTree; - FColumns: TVirtualTreeColumns; - FHeight: TDimension; - FFont: TFont; - FParentFont: Boolean; - FOptions: TVTHeaderOptions; - FStyle: TVTHeaderStyle; // button style - FBackgroundColor: TColor; - FAutoSizeIndex: TColumnIndex; - FPopupMenu: TPopupMenu; - FMainColumn: TColumnIndex; // the column which holds the tree - FMaxHeight: TDimension; - FMinHeight: TDimension; - FDefaultHeight: TDimension; - FFixedAreaConstraints: TVTFixedAreaConstraints; // Percentages for the fixed area (header, fixed columns). - FImages: TCustomImageList; - FImageChangeLink: TChangeLink; // connections to the image list to get notified about changes - fSplitterHitTolerance: TDimension; // For property SplitterHitTolerance - FSortColumn: TColumnIndex; - FSortDirection: TSortDirection; - FDragImage: TVTDragImage; // drag image management during header drag - FLastWidth: TDimension; // Used to adjust spring columns. This is the width of all visible columns, not the header rectangle. - FRestoreSelectionColumnIndex: Integer; // The column that is used to implement the coRestoreSelection option - function GetMainColumn: TColumnIndex; - function GetUseColumns: Boolean; - function IsFontStored: Boolean; - procedure SetAutoSizeIndex(Value: TColumnIndex); - procedure SetBackground(Value: TColor); - procedure SetColumns(Value: TVirtualTreeColumns); - procedure SetDefaultHeight(Value: TDimension); - procedure SetFont(const Value: TFont); - procedure SetHeight(Value: TDimension); - procedure SetImages(const Value: TCustomImageList); - procedure SetMainColumn(Value: TColumnIndex); - procedure SetMaxHeight(Value: TDimension); - procedure SetMinHeight(Value: TDimension); - procedure SetOptions(Value: TVTHeaderOptions); - procedure SetParentFont(Value: Boolean); - procedure SetSortColumn(Value: TColumnIndex); - procedure SetSortDirection(const Value: TSortDirection); - procedure SetStyle(Value: TVTHeaderStyle); - function GetRestoreSelectionColumnIndex: Integer; - function AreColumnsStored: Boolean; - protected - FStates: THeaderStates; // Used to keep track of internal states the header can enter. - FDragStart: TPoint; // initial mouse drag position - FTrackStart: TPoint; // client coordinates of the tracking start point - FTrackPoint: TPoint; // Client coordinate where the tracking started. - FDoingAutoFitColumns: boolean; // Flag to avoid using the stored width for Main column - - procedure FontChanged(Sender: TObject); virtual; - procedure AutoScale(); virtual; - function CanSplitterResize(P: TPoint): Boolean; - function CanWriteColumns: Boolean; virtual; - procedure ChangeScale(M, D: TDimension; isDpiChange: Boolean); virtual; - function DetermineSplitterIndex(P: TPoint): Boolean; virtual; - procedure DoAfterAutoFitColumn(Column: TColumnIndex); virtual; - procedure DoAfterColumnWidthTracking(Column: TColumnIndex); virtual; - procedure DoAfterHeightTracking; virtual; - function DoBeforeAutoFitColumn(Column: TColumnIndex; SmartAutoFitType: TSmartAutoFitType): Boolean; virtual; - procedure DoBeforeColumnWidthTracking(Column: TColumnIndex; Shift: TShiftState); virtual; - procedure DoBeforeHeightTracking(Shift: TShiftState); virtual; - procedure DoCanSplitterResize(P: TPoint; var Allowed: Boolean); virtual; - function DoColumnWidthDblClickResize(Column: TColumnIndex; P: TPoint; Shift: TShiftState): Boolean; virtual; - function DoColumnWidthTracking(Column: TColumnIndex; Shift: TShiftState; var TrackPoint: TPoint; P: TPoint): Boolean; virtual; - function DoGetPopupMenu(Column: TColumnIndex; Position: TPoint): TPopupMenu; virtual; - function DoHeightTracking(var P: TPoint; Shift: TShiftState): Boolean; virtual; - function DoHeightDblClickResize(var P: TPoint; Shift: TShiftState): Boolean; virtual; - procedure DoSetSortColumn(Value: TColumnIndex; pSortDirection: TSortDirection); virtual; - procedure DragTo(P: TPoint); virtual; - procedure FixedAreaConstraintsChanged(Sender: TObject); - function GetColumnsClass: TVirtualTreeColumnsClass; virtual; - function GetOwner: TPersistent; override; - function GetShiftState: TShiftState; - function HandleHeaderMouseMove(var Message: TWMMouseMove): Boolean; - function HandleMessage(var Message: TMessage): Boolean; virtual; - procedure ImageListChange(Sender: TObject); - procedure PrepareDrag(P, Start: TPoint); - procedure ReadColumns(Reader: TReader); - procedure RecalculateHeader; virtual; - procedure RescaleHeader; - procedure UpdateMainColumn; - procedure UpdateSpringColumns; - procedure WriteColumns(Writer: TWriter); - public - constructor Create(AOwner: TBaseVirtualTree); virtual; - destructor Destroy; override; - - function AllowFocus(ColumnIndex: TColumnIndex): Boolean; - procedure Assign(Source: TPersistent); override; - procedure AutoFitColumns(Animated: Boolean = True; SmartAutoFitType: TSmartAutoFitType = smaUseColumnOption; - RangeStartCol: Integer = NoColumn; RangeEndCol: Integer = NoColumn); virtual; - function InHeader(P: TPoint): Boolean; virtual; - function InHeaderSplitterArea(P: TPoint): Boolean; virtual; - procedure Invalidate(Column: TVirtualTreeColumn; ExpandToBorder: Boolean = False; UpdateNowFlag : Boolean = False); - procedure LoadFromStream(const Stream: TStream); virtual; - function ResizeColumns(ChangeBy: TDimension; RangeStartCol: TColumnIndex; RangeEndCol: TColumnIndex; - Options: TVTColumnOptions = [coVisible]): TDimension; - procedure RestoreColumns; - procedure SaveToStream(const Stream: TStream); virtual; - procedure StyleChanged(); virtual; - procedure ToggleSortDirection; - - property DragImage: TVTDragImage read FDragImage; - property RestoreSelectionColumnIndex: Integer read GetRestoreSelectionColumnIndex write fRestoreSelectionColumnIndex default NoColumn; - property States: THeaderStates read FStates; - property Treeview: TBaseVirtualTree read FOwner; - property UseColumns: Boolean read GetUseColumns; - property doingAutoFitColumns: boolean read FDoingAutoFitColumns; - published - property AutoSizeIndex: TColumnIndex read FAutoSizeIndex write SetAutoSizeIndex; - property Background: TColor read FBackgroundColor write SetBackground default clBtnFace; - property Columns: TVirtualTreeColumns read FColumns write SetColumns stored AreColumnsStored; - property DefaultHeight: Integer read FDefaultHeight write SetDefaultHeight default 19; - property Font: TFont read FFont write SetFont stored IsFontStored; - property FixedAreaConstraints: TVTFixedAreaConstraints read FFixedAreaConstraints write FFixedAreaConstraints; - property Height: Integer read FHeight write SetHeight default 19; - property Images: TCustomImageList read FImages write SetImages; - property MainColumn: TColumnIndex read GetMainColumn write SetMainColumn default 0; - property MaxHeight: Integer read FMaxHeight write SetMaxHeight default 10000; - property MinHeight: Integer read FMinHeight write SetMinHeight default 10; - property Options: TVTHeaderOptions read FOptions write SetOptions default [hoColumnResize, hoDrag, hoShowSortGlyphs]; - property ParentFont: Boolean read FParentFont write SetParentFont default True; - property PopupMenu: TPopupMenu read FPopupMenu write FPopupMenu; - property SortColumn: TColumnIndex read FSortColumn write SetSortColumn default NoColumn; - property SortDirection: TSortDirection read FSortDirection write SetSortDirection default sdAscending; - property SplitterHitTolerance: Integer read fSplitterHitTolerance write fSplitterHitTolerance default 8; // The area in pixels around a spliter which is sensitive for resizing - property Style: TVTHeaderStyle read FStyle write SetStyle default hsThickButtons; - end; - - TVTHeaderClass = class of TVTHeader; - - // Communication interface between a tree editor and the tree itself (declared as using stdcall in case it - // is implemented in a (C/C++) DLL). The GUID is not nessecary in Delphi but important for BCB users - // to allow QueryInterface and _uuidof calls. - IVTEditLink = interface - ['{2BE3EAFA-5ACB-45B4-9D9A-B58BCC496E17}'] - function BeginEdit: Boolean; stdcall; // Called when editing actually starts. - function CancelEdit: Boolean; stdcall; // Called when editing has been cancelled by the tree. - function EndEdit: Boolean; stdcall; // Called when editing has been finished by the tree. Returns True if successful, False if edit mode is still active. - function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall; - // Called after creation to allow a setup. - procedure ProcessMessage(var Message: TMessage); stdcall; - // Used to forward messages to the edit window(s)- - procedure SetBounds(R: TRect); stdcall; // Called to place the editor. - end; - - // Indicates in the OnUpdating event what state the tree is currently in. - TVTUpdateState = ( - usBegin, // The tree just entered the update state (BeginUpdate call for the first time). - usBeginSynch, // The tree just entered the synch update state (BeginSynch call for the first time). - usSynch, // Begin/EndSynch has been called but the tree did not change the update state. - usUpdate, // Begin/EndUpdate has been called but the tree did not change the update state. - usEnd, // The tree just left the update state (EndUpdate called for the last level). - usEndSynch // The tree just left the synch update state (EndSynch called for the last level). - ); - - // These elements are used both to query the application, which of them it wants to draw itself and to tell it during - // painting, which elements must be drawn during the advanced custom draw events. - THeaderPaintElements = set of ( - hpeBackground, - hpeDropMark, - hpeHeaderGlyph, - hpeSortGlyph, - hpeText, - // New in 7.0: Use this in FOnHeaderDrawQueryElements and OnAdvancedHeaderDraw - // for additional custom header drawing while keeping the default drawing - hpeOverlay - ); - - // Various events must be handled at different places than they were initiated or need - // a persistent storage until they are reset. - TVirtualTreeStates = set of ( - tsChangePending, // A selection change is pending. - tsCheckPropagation, // Set during automatic check state propagation. - tsCollapsing, // A full collapse operation is in progress. - tsToggleFocusedSelection, // Node selection was modifed using Ctrl-click. Change selection state on next mouse up. - tsClearPending, // Need to clear the current selection on next mouse move. - tsClearOnNewSelection, // Need to clear the current selection before selecting a new node - tsClipboardFlushing, // Set during flushing the clipboard to avoid freeing the content. - tsCopyPending, // Indicates a pending copy operation which needs to be finished. - tsCutPending, // Indicates a pending cut operation which needs to be finished. - tsDrawSelPending, // Multiselection only. User held down the left mouse button on a free - // area and might want to start draw selection. - tsDrawSelecting, // Multiselection only. Draw selection has actually started. - tsEditing, // Indicates that an edit operation is currently in progress. - tsEditPending, // An mouse up start edit if dragging has not started. - tsExpanding, // A full expand operation is in progress. - tsNodeHeightTracking, // A node height changing operation is in progress. - tsNodeHeightTrackPending, // left button is down, user might want to start changing a node's height. - tsHint, // Set when our hint is visible or soon will be. - tsInAnimation, // Set if the tree is currently in an animation loop. - tsIncrementalSearching, // Set when the user starts incremental search. - tsIncrementalSearchPending, // Set in WM_KEYDOWN to tell to use the char in WM_CHAR for incremental search. - tsIterating, // Set when IterateSubtree is currently in progress. - tsLeftButtonDown, // Set when the left mouse button is down. - tsLeftDblClick, // Set when the left mouse button was doubly clicked. - tsMiddleButtonDown, // Set when the middle mouse button is down. - tsMiddleDblClick, // Set when the middle mouse button was doubly clicked. - tsNeedRootCountUpdate, // Set if while loading a root node count is set. - tsOLEDragging, // OLE dragging in progress. - tsOLEDragPending, // User has requested to start delayed dragging. - tsPainting, // The tree is currently painting itself. - tsRightButtonDown, // Set when the right mouse button is down. - tsRightDblClick, // Set when the right mouse button was doubly clicked. - tsPopupMenuShown, // The user clicked the right mouse button, which might cause a popup menu to appear. - tsScrolling, // Set when autoscrolling is active. - tsScrollPending, // Set when waiting for the scroll delay time to elapse. - tsSizing, // Set when the tree window is being resized. This is used to prevent recursive calls - // due to setting the scrollbars when sizing. - tsStopValidation, // Cache validation can be stopped (usually because a change has occured meanwhile). - tsStructureChangePending, // The structure of the tree has been changed while the update was locked. - tsSynchMode, // Set when the tree is in synch mode, where no timer events are triggered. - tsThumbTracking, // Stop updating the horizontal scroll bar while dragging the vertical thumb and vice versa. - tsToggling, // A toggle operation (for some node) is in progress. - tsUpdateHiddenChildrenNeeded, // Pending update for the hidden children flag after massive visibility changes. - tsUpdating, // The tree does currently not update its window because a BeginUpdate has not yet ended. - tsUseCache, // The tree's node caches are validated and non-empty. - tsUserDragObject, // Signals that the application created an own drag object in OnStartDrag. - tsUseThemes, // The tree runs under WinXP+, is theme aware and themes are enabled. - tsValidating, // The tree's node caches are currently validated. - tsPreviouslySelectedLocked,// The member FPreviouslySelected should not be changed - tsValidationNeeded, // Something in the structure of the tree has changed. The cache needs validation. - tsVCLDragging, // VCL drag'n drop in progress. - tsVCLDragPending, // One-shot flag to avoid clearing the current selection on implicit mouse up for VCL drag. - tsVCLDragFinished, // Flag to avoid triggering the OnColumnClick event twice - tsWheelPanning, // Wheel mouse panning is active or soon will be. - tsWheelScrolling, // Wheel mouse scrolling is active or soon will be. - tsWindowCreating, // Set during window handle creation to avoid frequent unnecessary updates. - tsUseExplorerTheme // The tree runs under WinVista+ and is using the explorer theme - ); - - // determines whether and how the drag image is to show - TVTDragImageKind = ( - diComplete, // show a complete drag image with all columns, only visible columns are shown - diMainColumnOnly, // show only the main column (the tree column) - diNoImage // don't show a drag image at all - ); - - // Switch for OLE and VCL drag'n drop. Because it is not possible to have both simultanously. - TVTDragType = ( - dtOLE, - dtVCL - ); - - // options which determine what to draw in PaintTree - TVTInternalPaintOption = ( - poBackground, // draw background image if there is any and it is enabled - poColumnColor, // erase node's background with the column's color - poDrawFocusRect, // draw focus rectangle around the focused node - poDrawSelection, // draw selected nodes with the normal selection color - poDrawDropMark, // draw drop mark if a node is currently the drop target - poGridLines, // draw grid lines if enabled - poMainOnly, // draw only the main column - poSelectedOnly, // draw only selected nodes - poUnbuffered // draw directly onto the target canvas; especially useful when printing - ); - TVTInternalPaintOptions = set of TVTInternalPaintOption; - - // Determines the look of a tree's lines. - TVTLineStyle = ( - lsCustomStyle, // application provides a line pattern - lsDotted, // usual dotted lines (default) - lsSolid // simple solid lines - ); - - // TVTLineType is used during painting a tree - TVTLineType = ( - ltNone, // no line at all - ltBottomRight, // a line from bottom to the center and from there to the right - ltTopDown, // a line from top to bottom - ltTopDownRight, // a line from top to bottom and from center to the right - ltRight, // a line from center to the right - ltTopRight, // a line from bottom to center and from there to the right - // special styles for alternative drawings of tree lines - ltLeft, // a line from top to bottom at the left - ltLeftBottom // a combination of ltLeft and a line at the bottom from left to right - ); - - // Determines how to draw tree lines. - TVTLineMode = ( - lmNormal, // usual tree lines (as in TTreeview) - lmBands // looks similar to a Nassi-Schneidermann diagram - ); - - // A collection of line type IDs which is used while painting a node. - TLineImage = array of TVTLineType; - - TVTScrollIncrement = 1..10000; - - // Export type - TVTExportType = ( - etNone, // No export, normal displaying on the screen - etRTF, // contentToRTF - etHTML, // contentToHTML - etText, // contentToText - etExcel, // supported by external tools - etWord, // supported by external tools - etPDF, // supported by external tools - etPrinter,// supported by external tools - etCSV, // supported by external tools - etCustom // supported by external tools - ); - - TVTNodeExportEvent = procedure (Sender: TBaseVirtualTree; aExportType: TVTExportType; Node: PVirtualNode) of object; - TVTColumnExportEvent = procedure (Sender: TBaseVirtualTree; aExportType: TVTExportType; Column: TVirtualTreeColumn) of object; - TVTTreeExportEvent = procedure(Sender: TBaseVirtualTree; aExportType: TVTExportType) of object; - - // A class to manage scroll bar aspects. - TScrollBarOptions = class(TPersistent) - private - FAlwaysVisible: Boolean; - FOwner: TBaseVirtualTree; - FScrollBars: TScrollStyle; // used to hide or show vertical and/or horizontal scrollbar - FScrollBarStyle: TScrollBarStyle; // kind of scrollbars to use - FIncrementX, - FIncrementY: TVTScrollIncrement; // number of pixels to scroll in one step (when auto scrolling) - procedure SetAlwaysVisible(Value: Boolean); - procedure SetScrollBars(Value: TScrollStyle); - procedure SetScrollBarStyle(Value: TScrollBarStyle); - protected - function GetOwner: TPersistent; override; - public - constructor Create(AOwner: TBaseVirtualTree); - - procedure Assign(Source: TPersistent); override; - published - property AlwaysVisible: Boolean read FAlwaysVisible write SetAlwaysVisible default False; - property HorizontalIncrement: TVTScrollIncrement read FIncrementX write FIncrementX default 20; - property ScrollBars: TScrollStyle read FScrollBars write SetScrollBars default ssBoth; - property ScrollBarStyle: TScrollBarStyle read FScrollBarStyle write SetScrollBarStyle default sbmRegular; - property VerticalIncrement: TVTScrollIncrement read FIncrementY write FIncrementY default 20; - end; - - // class to collect all switchable colors into one place - TVTColors = class(TPersistent) - private - type - TVTColorEnum =(cDisabledColor, cDropMarkColor, cDropTargetColor, cFocusedSelectionColor, - cGridLineColor, cTreeLineColor, cUnfocusedSelectionColor, cBorderColor, cHotColor, - cFocusedSelectionBorderColor, cUnfocusedSelectionBorderColor, cDropTargetBorderColor, - cSelectionRectangleBlendColor, cSelectionRectangleBorderColor, cHeaderHotColor, - cSelectionTextColor, cUnfocusedColor); - - // Please make sure that the published Color properties at the corresponding index - // have the same color if you change anything here! - const cDefaultColors : array[TVTColorEnum] of TColor = ( - clBtnShadow, // DisabledColor - clHighlight, // DropMarkColor - clHighLight, // DropTargetColor - clHighLight, // FocusedSelectionColor - clBtnFace, // GridLineColor - clBtnShadow, // TreeLineColor - clInactiveCaption, // UnfocusedSelectionColor - clBtnFace, // BorderColor - clWindowText, // HotColor - clHighLight, // FocusedSelectionBorderColor - clInactiveCaption, // UnfocusedSelectionBorderColor - clHighlight, // DropTargetBorderColor - clHighlight, // SelectionRectangleBlendColor - clHighlight, // SelectionRectangleBorderColor - clBtnShadow, // HeaderHotColor - clHighlightText, // SelectionTextColor - clInactiveCaptionText); // UnfocusedColor [IPK] - - private - FOwner: TBaseVirtualTree; - FColors: array[TVTColorEnum] of TColor; // [IPK] 15 -> 16 - function GetColor(const Index: TVTColorEnum): TColor; - procedure SetColor(const Index: TVTColorEnum; const Value: TColor); - function GetBackgroundColor: TColor; - function GetHeaderFontColor: TColor; - function GetNodeFontColor: TColor; - function GetSelectedNodeFontColor(Focused:boolean): TColor; - public - constructor Create(AOwner: TBaseVirtualTree); - - procedure Assign(Source: TPersistent); override; - property BackGroundColor: TColor read GetBackgroundColor; - property HeaderFontColor: TColor read GetHeaderFontColor; - property NodeFontColor: TColor read GetNodeFontColor; - // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) - function StyleServices(AControl: TControl = nil): TCustomStyleServices; - published - property BorderColor: TColor index cBorderColor read GetColor write SetColor default clBtnFace; - property DisabledColor: TColor index cDisabledColor read GetColor write SetColor default clBtnShadow; - property DropMarkColor: TColor index cDropMarkColor read GetColor write SetColor default clHighlight; - property DropTargetColor: TColor index cDropTargetColor read GetColor write SetColor default clHighLight; - property DropTargetBorderColor: TColor index cDropTargetBorderColor read GetColor write SetColor default clHighLight; - /// The background color of selected nodes in case the tree has the focus, or the toPopupMode flag is set. - property FocusedSelectionColor: TColor index cFocusedSelectionColor read GetColor write SetColor default clHighLight; - /// The border color of selected nodes when the tree has the focus. - property FocusedSelectionBorderColor: TColor index cFocusedSelectionBorderColor read GetColor write SetColor default clHighLight; - property GridLineColor: TColor index cGridLineColor read GetColor write SetColor default clBtnFace; - property HeaderHotColor: TColor index cHeaderHotColor read GetColor write SetColor default clBtnShadow; - property HotColor: TColor index cHotColor read GetColor write SetColor default clWindowText; - property SelectionRectangleBlendColor: TColor index cSelectionRectangleBlendColor read GetColor write SetColor default clHighlight; - property SelectionRectangleBorderColor: TColor index cSelectionRectangleBorderColor read GetColor write SetColor default clHighlight; - /// The text color of selected nodes - property SelectionTextColor: TColor index cSelectionTextColor read GetColor write SetColor default clHighlightText; - property TreeLineColor: TColor index cTreeLineColor read GetColor write SetColor default clBtnShadow; - property UnfocusedColor: TColor index cUnfocusedColor read GetColor write SetColor default clInactiveCaptionText; // [IPK] Added - /// The background color of selected nodes in case the tree does not have the focus and the toPopupMode flag is not set. - property UnfocusedSelectionColor: TColor index cUnfocusedSelectionColor read GetColor write SetColor default clInactiveCaption; - /// The border color of selected nodes in case the tree does not have the focus and the toPopupMode flag is not set. - property UnfocusedSelectionBorderColor: TColor index cUnfocusedSelectionBorderColor read GetColor write SetColor default clInactiveCaption; - end; - - // For painting a node and its columns/cells a lot of information must be passed frequently around. - TVTImageInfo = record - Index: TImageIndex; // Index in the associated image list. - XPos, // Horizontal position in the current target canvas. - YPos: TDimension; // Vertical position in the current target canvas. - Ghosted: Boolean; // Flag to indicate that the image must be drawn slightly lighter. - Images: TCustomImageList; // The image list to be used for painting. - function Equals(const pImageInfo2: TVTImageInfo): Boolean; - end; - - TVTImageInfoIndex = ( - iiNormal, - iiState, - iiCheck, - iiOverlay - ); - - // Options which are used when modifying the scroll offsets. - TScrollUpdateOptions = set of ( - suoRepaintHeader, // if suoUpdateNCArea is also set then invalidate the header - suoRepaintScrollBars, // if suoUpdateNCArea is also set then repaint both scrollbars after updating them - suoScrollClientArea, // scroll and invalidate the proper part of the client area - suoUpdateNCArea // update non-client area (scrollbars, header) - ); - - // Determines the look of a tree's buttons. - TVTButtonStyle = ( - bsRectangle, // traditional Windows look (plus/minus buttons) - bsTriangle // traditional Macintosh look - ); - - // TButtonFillMode is only used when the button style is bsRectangle and determines how to fill the interior. - TVTButtonFillMode = ( - fmTreeColor, // solid color, uses the tree's background color - fmWindowColor, // solid color, uses clWindow - fmShaded, // color gradient, Windows XP style (legacy code, use toThemeAware on Windows XP instead) - fmTransparent // transparent color, use the item's background color - ); - - TVTPaintInfo = record - Canvas: TCanvas; // the canvas to paint on - PaintOptions: TVTInternalPaintOptions; // a copy of the paint options passed to PaintTree - Node: PVirtualNode; // the node to paint - Column: TColumnIndex; // the node's column index to paint - Position: TColumnPosition; // the column position of the node - CellRect: TRect; // the node cell - ContentRect: TRect; // the area of the cell used for the node's content - NodeWidth: TDimension; // the actual node width - Alignment: TAlignment; // how to align within the node rectangle - CaptionAlignment: TAlignment; // how to align text within the caption rectangle - BidiMode: TBidiMode; // directionality to be used for painting - BrushOrigin: TPoint; // the alignment for the brush used to draw dotted lines - ImageInfo: array[TVTImageInfoIndex] of TVTImageInfo; // info about each possible node image - Offsets: TVTOffsets; // The offsets of the various elements of a tree node - VAlign: TDimension; - procedure AdjustImageCoordinates(); - end; - - // Method called by the Animate routine for each animation step. - TVTAnimationCallback = function(Step, StepSize: Integer; Data: Pointer): Boolean of object; - - TVTIncrementalSearch = ( - isAll, // search every node in tree, initialize if necessary - isNone, // disable incremental search - isInitializedOnly, // search only initialized nodes, skip others - isVisibleOnly // search only visible nodes, initialize if necessary - ); - - // Determines which direction to use when advancing nodes during an incremental search. - TVTSearchDirection = ( - sdForward, - sdBackward - ); - - // Determines where to start incremental searching for each key press. - TVTSearchStart = ( - ssAlwaysStartOver, // always use the first/last node (depending on direction) to search from - ssLastHit, // use the last found node - ssFocusedNode // use the currently focused node - ); - - // Determines how to use the align member of a node. - TVTNodeAlignment = ( - naFromBottom, // the align member specifies amount of units (usually pixels) from top border of the node - naFromTop, // align is to be measured from bottom - naProportional // align is to be measure in percent of the entire node height and relative to top - ); - - // Determines how to draw the selection rectangle used for draw selection. - TVTDrawSelectionMode = ( - smDottedRectangle, // same as DrawFocusRect - smBlendedRectangle // alpha blending, uses special colors (see TVTColors) - ); - - // Determines for which purpose the cell paint event is called. - TVTCellPaintMode = ( - cpmPaint, // painting the cell - cpmGetContentMargin // getting cell content margin - ); - - // Determines which sides of the cell content margin should be considered. - TVTCellContentMarginType = ( - ccmtAllSides, // consider all sides - ccmtTopLeftOnly, // consider top margin and left margin only - ccmtBottomRightOnly // consider bottom margin and right margin only - ); - - TClipboardFormats = class(TStringList) - private - FOwner: TBaseVirtualTree; - public - constructor Create(AOwner: TBaseVirtualTree); virtual; - - function Add(const S: string): Integer; override; - procedure Insert(Index: Integer; const S: string); override; - property Owner: TBaseVirtualTree read FOwner; - end; - - // ----- Event prototypes: - - // node enumeration - TVTGetNodeProc = reference to procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean); - // node events - TVTChangingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; var Allowed: Boolean) of object; - TVTCheckChangingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; var NewState: TCheckState; - var Allowed: Boolean) of object; - TVTChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; - TVTStructureChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Reason: TChangeReason) of object; - TVTEditCancelEvent = procedure(Sender: TBaseVirtualTree; Column: TColumnIndex) of object; - TVTEditChangingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; - var Allowed: Boolean) of object; - TVTEditChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex) of object; - TVTFreeNodeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; - TVTFocusChangingEvent = procedure(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode; OldColumn, - NewColumn: TColumnIndex; var Allowed: Boolean) of object; - TVTFocusChangeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex) of object; - TVTAddToSelectionEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; - TVTRemoveFromSelectionEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; - TVTGetImageEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; - var Ghosted: Boolean; var ImageIndex: TImageIndex) of object; - TVTGetImageExEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; - var Ghosted: Boolean; var ImageIndex: TImageIndex; var ImageList: TCustomImageList) of object; - TVTGetImageTextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; - var ImageText: string) of object; - TVTHotNodeChangeEvent = procedure(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode) of object; - TVTInitChildrenEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; var ChildCount: Cardinal) of object; - TVTInitNodeEvent = procedure(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; - var InitialStates: TVirtualNodeInitStates) of object; - TVTPopupEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; const P: TPoint; - var AskParent: Boolean; var PopupMenu: TPopupMenu) of object; - TVTHelpContextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; - var HelpContext: Integer) of object; - TVTCreateEditorEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; - out EditLink: IVTEditLink) of object; - TVTSaveTreeEvent = procedure(Sender: TBaseVirtualTree; Stream: TStream) of object; - TVTSaveNodeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Stream: TStream) of object; - TVTBeforeGetCheckStateEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; - - // header/column events - TVTHeaderAddPopupItemEvent = procedure(const Sender: TBaseVirtualTree; const Column: TColumnIndex; - var Cmd: TAddPopupItemType) of object; - TVTHeaderClickEvent = procedure(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo) of object; - TVTHeaderMouseEvent = procedure(Sender: TVTHeader; Button: TMouseButton; Shift: TShiftState; X, Y: Integer) of object; - TVTHeaderMouseMoveEvent = procedure(Sender: TVTHeader; Shift: TShiftState; X, Y: Integer) of object; - TVTBeforeHeaderHeightTrackingEvent = procedure(Sender: TVTHeader; Shift: TShiftState) of object; - TVTAfterHeaderHeightTrackingEvent = procedure(Sender: TVTHeader) of object; - TVTHeaderHeightTrackingEvent = procedure(Sender: TVTHeader; var P: TPoint; Shift: TShiftState; var Allowed: Boolean) of object; - TVTHeaderHeightDblClickResizeEvent = procedure(Sender: TVTHeader; var P: TPoint; Shift: TShiftState; var Allowed: Boolean) of object; - TVTHeaderNotifyEvent = procedure(Sender: TVTHeader; Column: TColumnIndex) of object; - TVTHeaderDraggingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var Allowed: Boolean) of object; - TVTHeaderDraggedEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; OldPosition: Integer) of object; - TVTHeaderDraggedOutEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; DropPosition: TPoint) of object; - TVTHeaderPaintEvent = procedure(Sender: TVTHeader; HeaderCanvas: TCanvas; Column: TVirtualTreeColumn; R: TRect; Hover, - Pressed: Boolean; DropMark: TVTDropMarkMode) of object; - TVTHeaderPaintQueryElementsEvent = procedure(Sender: TVTHeader; var PaintInfo: THeaderPaintInfo; - var Elements: THeaderPaintElements) of object; - TVTAdvancedHeaderPaintEvent = procedure(Sender: TVTHeader; var PaintInfo: THeaderPaintInfo; - const Elements: THeaderPaintElements) of object; - TVTBeforeAutoFitColumnsEvent = procedure(Sender: TVTHeader; var SmartAutoFitType: TSmartAutoFitType) of object; - TVTBeforeAutoFitColumnEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var SmartAutoFitType: TSmartAutoFitType; - var Allowed: Boolean) of object; - TVTAfterAutoFitColumnEvent = procedure(Sender: TVTHeader; Column: TColumnIndex) of object; - TVTAfterAutoFitColumnsEvent = procedure(Sender: TVTHeader) of object; - TVTColumnClickEvent = procedure (Sender: TBaseVirtualTree; Column: TColumnIndex; Shift: TShiftState) of object; - TVTColumnDblClickEvent = procedure (Sender: TBaseVirtualTree; Column: TColumnIndex; Shift: TShiftState) of object; - TColumnChangeEvent = procedure(const Sender: TBaseVirtualTree; const Column: TColumnIndex; Visible: Boolean) of object; - TVTColumnWidthDblClickResizeEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; Shift: TShiftState; P: TPoint; - var Allowed: Boolean) of object; - TVTBeforeColumnWidthTrackingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; Shift: TShiftState) of object; - TVTAfterColumnWidthTrackingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex) of object; - TVTColumnWidthTrackingEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; Shift: TShiftState; var TrackPoint: TPoint; P: TPoint; - var Allowed: Boolean) of object; - TVTGetHeaderCursorEvent = procedure(Sender: TVTHeader; var Cursor: HCURSOR) of object; - TVTBeforeGetMaxColumnWidthEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var UseSmartColumnWidth: Boolean) of object; - TVTAfterGetMaxColumnWidthEvent = procedure(Sender: TVTHeader; Column: TColumnIndex; var MaxWidth: Integer) of object; - TVTCanSplitterResizeColumnEvent = procedure(Sender: TVTHeader; P: TPoint; Column: TColumnIndex; var Allowed: Boolean) of object; - TVTCanSplitterResizeHeaderEvent = procedure(Sender: TVTHeader; P: TPoint; var Allowed: Boolean) of object; - - // move, copy and node tracking events - TVTNodeMovedEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; - TVTNodeMovingEvent = procedure(Sender: TBaseVirtualTree; Node, Target: PVirtualNode; - var Allowed: Boolean) of object; - TVTNodeCopiedEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode) of object; - TVTNodeCopyingEvent = procedure(Sender: TBaseVirtualTree; Node, Target: PVirtualNode; - var Allowed: Boolean) of object; - TVTNodeClickEvent = procedure(Sender: TBaseVirtualTree; const HitInfo: THitInfo) of object; - TVTNodeHeightTrackingEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState; - var TrackPoint: TPoint; P: TPoint; var Allowed: Boolean) of object; - TVTNodeHeightDblClickResizeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; - Shift: TShiftState; P: TPoint; var Allowed: Boolean) of object; - TVTCanSplitterResizeNodeEvent = procedure(Sender: TBaseVirtualTree; P: TPoint; Node: PVirtualNode; - Column: TColumnIndex; var Allowed: Boolean) of object; - - // drag'n drop/OLE events - TVTCreateDragManagerEvent = procedure(Sender: TBaseVirtualTree; out DragManager: IVTDragManager) of object; - TVTCreateDataObjectEvent = procedure(Sender: TBaseVirtualTree; out IDataObject: IDataObject) of object; - TVTDragAllowedEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; - var Allowed: Boolean) of object; - TVTDragOverEvent = procedure(Sender: TBaseVirtualTree; Source: TObject; Shift: TShiftState; State: TDragState; - Pt: TPoint; Mode: TDropMode; var Effect: Integer; var Accept: Boolean) of object; - TVTDragDropEvent = procedure(Sender: TBaseVirtualTree; Source: TObject; DataObject: IDataObject; - Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode) of object; - TVTRenderOLEDataEvent = procedure(Sender: TBaseVirtualTree; const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; - ForClipboard: Boolean; var Result: HRESULT) of object; - TVTGetUserClipboardFormatsEvent = procedure(Sender: TBaseVirtualTree; var Formats: TFormatEtcArray) of object; - - // paint events - TVTBeforeItemEraseEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; ItemRect: TRect; - var ItemColor: TColor; var EraseAction: TItemEraseAction) of object; - TVTAfterItemEraseEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; - ItemRect: TRect) of object; - TVTBeforeItemPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; - ItemRect: TRect; var CustomDraw: Boolean) of object; - TVTAfterItemPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; - ItemRect: TRect) of object; - TVTBeforeCellPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; - Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect) of object; - TVTAfterCellPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; - Column: TColumnIndex; CellRect: TRect) of object; - TVTPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas) of object; - TVTBackgroundPaintEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; R: TRect; - var Handled: Boolean) of object; - TVTGetLineStyleEvent = procedure(Sender: TBaseVirtualTree; var Bits: Pointer) of object; - TVTMeasureItemEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; - var NodeHeight: Integer) of object; - - TVTPrepareButtonImagesEvent = procedure(Sender: TBaseVirtualTree; const APlusBM : TBitmap; const APlusHotBM :TBitmap; - const APlusSelectedHotBM :TBitmap; const AMinusBM : TBitmap; const AMinusHotBM : TBitmap; - const AMinusSelectedHotBM :TBitmap; var ASize : TSize) of object; - - // search, sort - TVTCompareEvent = procedure(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex; - var Result: Integer) of object; - TVTIncrementalSearchEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; const SearchText: string; - var Result: Integer) of object; - - // operations - TVTOperationEvent = procedure(Sender: TBaseVirtualTree; OperationKind: TVTOperationKind) of object; - - TVTHintKind = (vhkText, vhkOwnerDraw); - TVTHintKindEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var Kind: TVTHintKind) of object; - TVTDrawHintEvent = procedure(Sender: TBaseVirtualTree; HintCanvas: TCanvas; Node: PVirtualNode; R: TRect; Column: TColumnIndex) of object; - TVTGetHintSizeEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var R: TRect) of object; - - // miscellaneous - TVTBeforeDrawLineImageEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Level: Integer; var PosX: Integer) of object; - TVTGetNodeDataSizeEvent = procedure(Sender: TBaseVirtualTree; var NodeDataSize: Integer) of object; - TVTKeyActionEvent = procedure(Sender: TBaseVirtualTree; var CharCode: Word; var Shift: TShiftState; - var DoDefault: Boolean) of object; - TVTScrollEvent = procedure(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer) of object; - TVTUpdatingEvent = procedure(Sender: TBaseVirtualTree; State: TVTUpdateState) of object; - TVTGetCursorEvent = procedure(Sender: TBaseVirtualTree; var Cursor: TCursor) of object; - TVTStateChangeEvent = procedure(Sender: TBaseVirtualTree; Enter, Leave: TVirtualTreeStates) of object; - TVTGetCellIsEmptyEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; - var IsEmpty: Boolean) of object; - TVTScrollBarShowEvent = procedure(Sender: TBaseVirtualTree; Bar: Integer; Show: Boolean) of object; - - // Helper types for node iterations. - TGetFirstNodeProc = function: PVirtualNode of object; - TGetNextNodeProc = function(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode of object; - - TVZVirtualNodeEnumerationMode = ( - vneAll, - vneChecked, - vneChild, - vneCutCopy, - vneInitialized, - vneLeaf, - vneLevel, - vneNoInit, - vneSelected, - vneVisible, - vneVisibleChild, - vneVisibleNoInitChild, - vneVisibleNoInit - ); - - PVTVirtualNodeEnumeration = ^TVTVirtualNodeEnumeration; - - TVTVirtualNodeEnumerator = record - private - FNode: PVirtualNode; - FCanMoveNext: Boolean; - FEnumeration: PVTVirtualNodeEnumeration; - function GetCurrent: PVirtualNode; inline; - public - function MoveNext: Boolean; inline; - property Current: PVirtualNode read GetCurrent; - end; - - TVTVirtualNodeEnumeration = record - private - FMode: TVZVirtualNodeEnumerationMode; - FTree: TBaseVirtualTree; - // GetNextXxx parameters: - FConsiderChildrenAbove: Boolean; - FNode: PVirtualNode; - FNodeLevel: Cardinal; - FState: TCheckState; - FIncludeFiltered: Boolean; - public - function GetEnumerator: TVTVirtualNodeEnumerator; - private - function GetNext(Node: PVirtualNode): PVirtualNode; - end; - - - // ----- TBaseVirtualTree - TBaseVirtualTree = class(TCustomControl) - private - FTotalInternalDataSize: Cardinal; // Cache of the sum of the necessary internal data size for all tree - FBorderStyle: TBorderStyle; - FHeader: TVTHeader; - FRoot: PVirtualNode; - FDefaultNodeHeight, - FIndent: Cardinal; - FOptions: TCustomVirtualTreeOptions; - FUpdateCount: Cardinal; // update stopper, updates of the tree control are only done if = 0 - FSynchUpdateCount: Cardinal; // synchronizer, causes all events which are usually done via timers - // to happen immediately, regardless of the normal update state - FNodeDataSize: Integer; // number of bytes to allocate with each node (in addition to its base - // structure and the internal data), if -1 then do callback - FStates: TVirtualTreeStates; // various active/pending states the tree needs to consider - FLastSelected, - FFocusedNode: PVirtualNode; - FEditColumn, // column to be edited (focused node) - FFocusedColumn: TColumnIndex; // NoColumn if no columns are active otherwise the last hit column of - // the currently focused node - FHeightTrackPoint: TPoint; // Starting point of a node's height changing operation. - FHeightTrackNode: PVirtualNode; // Node which height is being changed. - FHeightTrackColumn: TColumnIndex; // Initial column where the height changing operation takes place. - FScrollDirections: TScrollDirections; // directions to scroll client area into depending on mouse position - FLastStructureChangeReason: TChangeReason; // Used for delayed structure change event. - FLastStructureChangeNode, // dito - FLastChangedNode, // used for delayed change event - FCurrentHotNode: PVirtualNode; // Node over which the mouse is hovering. - FCurrentHotColumn: TColumnIndex; // Column over which the mouse is hovering. - FHotNodeButtonHit: Boolean; // Indicates wether the mouse is hovering over the hot node's button. - FLastSelRect, - FNewSelRect: TRect; // used while doing draw selection - FHotCursor: TCursor; // can be set to additionally indicate the current hot node - FLastHitInfo: THitInfo; // The THitInfo of the last mouse-down event. - // in Win98 (slide) and Windows 2000 (fade)) - FHintMode: TVTHintMode; // determines the kind of the hint window - FHintData: TVTHintData; // used while preparing the hint window - FChangeDelay: Cardinal; // used to delay OnChange event - FEditDelay: Cardinal; // determines time to elapse before a node goes into edit mode - FPositionCache: TCache; // array which stores node references ordered by vertical positions - // (see also DoValidateCache for more information) - FVisibleCount: Cardinal; // number of currently visible nodes - FStartIndex: Cardinal; // index to start validating cache from - FSelection: TNodeArray; // list of currently selected nodes - FSelectionCount: Integer; // number of currently selected nodes (size of FSelection might differ) - FSelectionLocked: Boolean; // prevents the tree from changing the selection - FRangeAnchor: PVirtualNode; // anchor node for selection with the keyboard, determines start of a - // selection range - FCheckPropagationCount: Cardinal; // nesting level of check propagation (WL, 05.02.2004) - FLastSelectionLevel: Integer; // keeps the last node level for constrained multiselection - FDrawSelShiftState: TShiftState; // keeps the initial shift state when the user starts selection with - // the mouse - FEditLink: IVTEditLink; // used to comunicate with an application defined editor - FTempNodeCache: TNodeArray; // used at various places to hold temporarily a bunch of node refs. - FTempNodeCount: Cardinal; // number of nodes in FTempNodeCache - FBackground: TPicture; // A background image loadable at design and runtime. - FBackgroundImageTransparent: Boolean; // By default, this is off. When switched on, will try to draw the image - // transparent by using the color of the component as transparent color - - FMargin: Integer; // horizontal distance to border and columns - FTextMargin: Integer; // space between the node's text and its horizontal bounds - FBackgroundOffsetX, - FBackgroundOffsetY: Integer; // used to fine tune the position of the background image - FAnimationDuration: Cardinal; // specifies how long an animation shall take (expanding, hint) - FWantTabs: Boolean; // If True then the tree also consumes the tab key. - FNodeAlignment: TVTNodeAlignment; // determines how to interpret the align member of a node - FHeaderRect: TRect; // Space which the header currently uses in the control (window coords). - FLastHintRect: TRect; // Area which the mouse must leave to reshow a hint. - FUpdateRect: TRect; - FEmptyListMessage: string; // Optional message that will be displayed if no nodes exist in the control. - - // paint support and images - FPlusBM, - FMinusBM, // small bitmaps used for tree buttons - FHotPlusBM, - FHotMinusBM, - FSelectedHotPlusBM, - FSelectedHotMinusBM: TBitmap; // small bitmaps used for hot tree buttons - FImages, // normal images in the tree - FStateImages, // state images in the tree - FCustomCheckImages: TCustomImageList; // application defined check images - FCheckImageKind: TCheckImageKind; // light or dark, cross marks or tick marks - FCheckImages: TCustomImageList; // Reference to global image list to be used for the check images. - //TODO: Use this margin for other images as well - FImagesMargin: Integer; // The margin used left and right of the checkboxes. - FImageChangeLink, - FStateChangeLink, - FCustomCheckChangeLink: TChangeLink; // connections to the image lists - FOldFontChange: TNotifyEvent; // helper method pointer for tracking font changes in the off screen buffer - FColors: TVTColors; // class comprising all customizable colors in the tree - FButtonStyle: TVTButtonStyle; // style of the tree buttons - FButtonFillMode: TVTButtonFillMode; // for rectangular tree buttons only: how to fill them - FLineStyle: TVTLineStyle; // style of the tree lines - FLineMode: TVTLineMode; // tree lines or bands etc. - FDottedBrush: HBRUSH; // used to paint dotted lines without special pens - FSelectionCurveRadius: Cardinal; // radius for rounded selection rectangles - FSelectionBlendFactor: Byte; // Determines the factor by which the selection rectangle is to be - // faded if enabled. - FDrawSelectionMode: TVTDrawSelectionMode; // determines the paint mode for draw selection - - // alignment and directionality support - FAlignment: TAlignment; // default alignment of the tree if no columns are shown - - // drag'n drop and clipboard support - FDragImageKind: TVTDragImageKind; // determines whether or not and what to show in the drag image - FDragOperations: TDragOperations; // determines which operations are allowed during drag'n drop - FDragThreshold: Integer; // used to determine when to actually start a drag'n drop operation - FDragManager: IVTDragManager; // drag'n drop, cut'n paste - FDropTargetNode: PVirtualNode; // node currently selected as drop target - FLastDropMode: TDropMode; // set while dragging and used to track changes - FDragSelection: TNodeArray; // temporary copy of FSelection used during drag'n drop - FLastDragEffect: Integer; // The last executed drag effect - FDragType: TVTDragType; // used to switch between OLE and VCL drag'n drop - FDragImage: TVTDragImage; // drag image management - FDragWidth, - FDragHeight: Integer; // size of the drag image, the larger the more CPU power is needed - FClipboardFormats: TClipboardFormats; // a list of clipboard format descriptions enabled for this tree - FLastVCLDragTarget: PVirtualNode; // A node cache for VCL drag'n drop (keywords: DragLeave on DragDrop). - FVCLDragEffect: Integer; // A cache for VCL drag'n drop to keep the current drop effect. - - // scroll support - FScrollBarOptions: TScrollBarOptions; // common properties of horizontal and vertical scrollbar - FAutoScrollInterval: TAutoScrollInterval; // determines speed of auto scrolling - FAutoScrollDelay: Cardinal; // amount of milliseconds to wait until autoscrolling becomes active - FAutoExpandDelay: Cardinal; // amount of milliseconds to wait until a node is expanded if it is the - // drop target - FOffsetX: Integer; - FOffsetY: Integer; // Determines left and top scroll offset. - FEffectiveOffsetX: Integer; // Actual position of the horizontal scroll bar (varies depending on bidi mode). - FRangeX, - FRangeY: Cardinal; // current virtual width and height of the tree - FBottomSpace: Cardinal; // Extra space below the last node. - - FDefaultPasteMode: TVTNodeAttachMode; // Used to determine where to add pasted nodes to. - FDragScrollStart: Cardinal; // Contains the start time when a tree does auto scrolling as drop target. - - // search - FIncrementalSearch: TVTIncrementalSearch; // Used to determine whether and how incremental search is to be used. - FSearchTimeout: Cardinal; // Number of milliseconds after which to stop incremental searching. - FSearchBuffer: string; // Collects a sequence of keypresses used to do incremental searching. - FLastSearchNode: PVirtualNode; // Reference to node which was last found as search fit. - FSearchDirection: TVTSearchDirection; // Direction to incrementally search the tree. - FSearchStart: TVTSearchStart; // Where to start iteration on each key press. - - // miscellanous - FPanningWindow: HWND; // Helper window for wheel panning - FPanningCursor: HCURSOR; // Current wheel panning cursor. - FPanningImage: TBitmap; // A little 32x32 bitmap to indicate the panning reference point. - FLastClickPos: TPoint; // Used for retained drag start and wheel mouse scrolling. - FOperationCount: Cardinal; // Counts how many nested long-running operations are in progress. - FOperationCanceled: Boolean; // Used to indicate that a long-running operation should be canceled. - FChangingTheme: Boolean; // Used to indicate that a theme change is goi ng on - FNextNodeToSelect: PVirtualNode; // Next tree node that we would like to select if the current one gets deleted or looses selection for other reasons. - - // MSAA support - FAccessible: IAccessible; // The IAccessible interface to the window itself. - FAccessibleItem: IAccessible; // The IAccessible to the item that currently has focus. - FAccessibleName: string; // The name the window is given for screen readers. - - // export - FOnBeforeNodeExport: TVTNodeExportEvent; // called before exporting a node - FOnNodeExport: TVTNodeExportEvent; - FOnAfterNodeExport: TVTNodeExportEvent; // called after exporting a node - FOnBeforeColumnExport: TVTColumnExportEvent; // called before exporting a column - FOnColumnExport: TVTColumnExportEvent; - FOnAfterColumnExport: TVTColumnExportEvent; // called after exporting a column - FOnBeforeTreeExport: TVTTreeExportEvent; // called before starting the export - FOnAfterTreeExport: TVTTreeExportEvent; // called after finishing the export - FOnBeforeHeaderExport: TVTTreeExportEvent; // called before exporting the header - FOnAfterHeaderExport: TVTTreeExportEvent; // called after exporting the header - - // common events - FOnChange: TVTChangeEvent; // selection change - FOnStructureChange: TVTStructureChangeEvent; // structural change like adding nodes etc. - FOnInitChildren: TVTInitChildrenEvent; // called when a node's children are needed (expanding etc.) - FOnInitNode: TVTInitNodeEvent; // called when a node needs to be initialized (child count etc.) - FOnFreeNode: TVTFreeNodeEvent; // called when a node is about to be destroyed, user data can and should - // be freed in this event - FOnGetImage: TVTGetImageEvent; // Used to retrieve the image index of a given node. - FOnGetImageEx: TVTGetImageExEvent; // Used to retrieve the image index of a given node along with a custom - // image list. - FOnGetImageText: TVTGetImageTextEvent; // Used to retrieve the image alternative text of a given node. - // Used by the accessibility interface to provide useful text for status images. - FOnHotChange: TVTHotNodeChangeEvent; // called when the current "hot" node (that is, the node under the mouse) - // changes and hot tracking is enabled - FOnExpanding, // called just before a node is expanded - FOnCollapsing: TVTChangingEvent; // called just before a node is collapsed - FOnChecking: TVTCheckChangingEvent; // called just before a node's check state is changed - FOnExpanded, // called after a node has been expanded - FOnCollapsed, // called after a node has been collapsed - FOnChecked: TVTChangeEvent; // called after a node's check state has been changed - FOnResetNode: TVTChangeEvent; // called when a node is set to be uninitialized - FOnNodeMoving: TVTNodeMovingEvent; // called just before a node is moved from one parent node to another - // (this can be cancelled) - FOnNodeMoved: TVTNodeMovedEvent; // called after a node and its children have been moved to another - // parent node (probably another tree, but within the same application) - FOnNodeCopying: TVTNodeCopyingEvent; // called when a node is copied to another parent node (probably in - // another tree, but within the same application, can be cancelled) - FOnNodeClick: TVTNodeClickEvent; // called when the user clicks on a node - FOnNodeDblClick: TVTNodeClickEvent; // called when the user double clicks on a node - FOnCanSplitterResizeNode: TVTCanSplitterResizeNodeEvent; // called to query the application wether resizing a node is allowed - FOnNodeHeightTracking: TVTNodeHeightTrackingEvent; // called when a node's height is being changed via mouse - FOnNodeHeightDblClickResize: TVTNodeHeightDblClickResizeEvent; // called when a node's vertical splitter is double clicked - FOnNodeCopied: TVTNodeCopiedEvent; // call after a node has been copied - FOnEditing: TVTEditChangingEvent; // called just before a node goes into edit mode - FOnEditCancelled: TVTEditCancelEvent; // called when editing has been cancelled - FOnEdited: TVTEditChangeEvent; // called when editing has successfully been finished - FOnFocusChanging: TVTFocusChangingEvent; // called when the focus is about to go to a new node and/or column - // (can be cancelled) - FOnFocusChanged: TVTFocusChangeEvent; // called when the focus goes to a new node and/or column - FOnAddToSelection: TVTAddToSelectionEvent; // called when a node is added to the selection - FOnRemoveFromSelection: TVTRemoveFromSelectionEvent; // called when a node is removed from the selection - FOnGetPopupMenu: TVTPopupEvent; // called when the popup for a node or the header needs to be shown - FOnGetHelpContext: TVTHelpContextEvent; // called when a node specific help theme should be called - FOnCreateEditor: TVTCreateEditorEvent; // called when a node goes into edit mode, this allows applications - // to supply their own editor - FOnLoadNode, // called after a node has been loaded from a stream (file, clipboard, - // OLE drag'n drop) to allow an application to load their own data - // saved in OnSaveNode - FOnSaveNode: TVTSaveNodeEvent; // called when a node needs to be serialized into a stream - // (see OnLoadNode) to give the application the opportunity to save - // their node specific, persistent data (note: never save memory - // references) - FOnLoadTree, // called after the tree has been loaded from a stream to allow an - // application to load their own data saved in OnSaveTree - FOnSaveTree: TVTSaveTreeEvent; // called after the tree has been saved to a stream to allow an - // application to save its own data - - // header/column mouse events - FOnAfterAutoFitColumn: TVTAfterAutoFitColumnEvent; - FOnAfterAutoFitColumns: TVTAfterAutoFitColumnsEvent; - FOnBeforeAutoFitColumns: TVTBeforeAutoFitColumnsEvent; - FOnBeforeAutoFitColumn: TVTBeforeAutoFitColumnEvent; - FOnHeaderAddPopupItem: TVTHeaderAddPopupItemEvent; - FOnHeaderClick: TVTHeaderClickEvent; - FOnHeaderDblClick: TVTHeaderClickEvent; - FOnAfterHeaderHeightTracking: TVTAfterHeaderHeightTrackingEvent; - FOnBeforeHeaderHeightTracking: TVTBeforeHeaderHeightTrackingEvent; - FOnHeaderHeightTracking: TVTHeaderHeightTrackingEvent; - FOnHeaderHeightDblClickResize: TVTHeaderHeightDblClickResizeEvent; - FOnHeaderMouseDown, - FOnHeaderMouseUp: TVTHeaderMouseEvent; - FOnHeaderMouseMove: TVTHeaderMouseMoveEvent; - FOnAfterGetMaxColumnWidth: TVTAfterGetMaxColumnWidthEvent; - FOnBeforeGetMaxColumnWidth: TVTBeforeGetMaxColumnWidthEvent; - FOnColumnClick: TVTColumnClickEvent; - FOnColumnDblClick: TVTColumnDblClickEvent; - FOnColumnResize: TVTHeaderNotifyEvent; - fOnColumnVisibilityChanged: TColumnChangeEvent; - FOnColumnWidthDblClickResize: TVTColumnWidthDblClickResizeEvent; - FOnAfterColumnWidthTracking: TVTAfterColumnWidthTrackingEvent; - FOnBeforeColumnWidthTracking: TVTBeforeColumnWidthTrackingEvent; - FOnColumnWidthTracking: TVTColumnWidthTrackingEvent; - FOnGetHeaderCursor: TVTGetHeaderCursorEvent; // triggered to allow the app. to use customized cursors for the header - FOnCanSplitterResizeColumn: TVTCanSplitterResizeColumnEvent; - FOnCanSplitterResizeHeader: TVTCanSplitterResizeHeaderEvent; - - // paint events - FOnAfterPaint, // triggered when the tree has entirely been painted - FOnBeforePaint: TVTPaintEvent; // triggered when the tree is about to be painted - FOnAfterItemPaint: TVTAfterItemPaintEvent; // triggered after an item has been painted - FOnBeforeItemPaint: TVTBeforeItemPaintEvent; // triggered when an item is about to be painted - FOnBeforeItemErase: TVTBeforeItemEraseEvent; // triggered when an item's background is about to be erased - FOnAfterItemErase: TVTAfterItemEraseEvent; // triggered after an item's background has been erased - FOnAfterCellPaint: TVTAfterCellPaintEvent; // triggered after a column of an item has been painted - FOnBeforeCellPaint: TVTBeforeCellPaintEvent; // triggered when a column of an item is about to be painted - FOnHeaderDraw: TVTHeaderPaintEvent; // Used when owner draw is enabled for the header and a column is set - // to owner draw mode. - FOnPrepareButtonImages : TVTPrepareButtonImagesEvent; //allow use to customise plus/minus bitmap images - FOnHeaderDrawQueryElements: TVTHeaderPaintQueryElementsEvent; // Used for advanced header painting to query the - // application for the elements, which are drawn by it and which should - // be drawn by the tree. - FOnAdvancedHeaderDraw: TVTAdvancedHeaderPaintEvent; // Used when owner draw is enabled for the header and a column - // is set to owner draw mode. But only if OnHeaderDrawQueryElements - // returns at least one element to be drawn by the application. - // In this case OnHeaderDraw is not used. - FOnGetLineStyle: TVTGetLineStyleEvent; // triggered when a custom line style is used and the pattern brush - // needs to be build - FOnPaintBackground: TVTBackgroundPaintEvent; // triggered if a part of the tree's background must be erased which is - // not covered by any node - FOnMeasureItem: TVTMeasureItemEvent; // Triggered when a node is about to be drawn and its height was not yet - // determined by the application. - - // drag'n drop events - FOnCreateDragManager: TVTCreateDragManagerEvent; // called to allow for app./descendant defined drag managers - FOnCreateDataObject: TVTCreateDataObjectEvent; // called to allow for app./descendant defined data objects - FOnDragAllowed: TVTDragAllowedEvent; // used to get permission for manual drag in mouse down - FOnDragOver: TVTDragOverEvent; // called for every mouse move - FOnDragDrop: TVTDragDropEvent; // called on release of mouse button (if drop was allowed) - FOnHeaderDragged: TVTHeaderDraggedEvent; // header (column) drag'n drop - FOnHeaderDraggedOut: TVTHeaderDraggedOutEvent; // header (column) drag'n drop, which did not result in a valid drop. - FOnHeaderDragging: TVTHeaderDraggingEvent; // header (column) drag'n drop - FOnRenderOLEData: TVTRenderOLEDataEvent; // application/descendant defined clipboard formats - FOnGetUserClipboardFormats: TVTGetUserClipboardFormatsEvent; // gives application/descendants the opportunity to - // add own clipboard formats on the fly - - // miscellanous events - FOnGetNodeDataSize: TVTGetNodeDataSizeEvent; // Called if NodeDataSize is -1. - FOnBeforeDrawLineImage: TVTBeforeDrawLineImageEvent; // Called to allow adjusting the indention of treelines. - FOnKeyAction: TVTKeyActionEvent; // Used to selectively prevent key actions (full expand on Ctrl+'+' etc.). - FOnScroll: TVTScrollEvent; // Called when one or both paint offsets changed. - FOnUpdating: TVTUpdatingEvent; // Called from BeginUpdate, EndUpdate, BeginSynch and EndSynch. - FOnGetCursor: TVTGetCursorEvent; // Called to allow the app. to set individual cursors. - FOnStateChange: TVTStateChangeEvent; // Called whenever a state in the tree changes. - FOnGetCellIsEmpty: TVTGetCellIsEmptyEvent; // Called when the tree needs to know if a cell is empty. - FOnShowScrollBar: TVTScrollBarShowEvent; // Called when a scrollbar is changed in its visibility. - FOnBeforeGetCheckState: TVTBeforeGetCheckStateEvent; // Called before a CheckState for a Node is obtained. - // Gives the application a chance to do special processing - // when a check state is actually required for the first time. - - // search, sort - FOnCompareNodes: TVTCompareEvent; // used during sort - FOnDrawHint: TVTDrawHintEvent; - FOnGetHintSize: TVTGetHintSizeEvent; - FOnGetHintKind: TVTHintKindEvent; - FOnIncrementalSearch: TVTIncrementalSearchEvent; // triggered on every key press (not key down) - FOnMouseEnter: TNotifyEvent; - FOnMouseLeave: TNotifyEvent; - - // operations - FOnStartOperation: TVTOperationEvent; // Called when an operation starts - FOnEndOperation: TVTOperationEvent; // Called when an operation ends - - FVclStyleEnabled: Boolean; - - procedure CMStyleChanged(var Message: TMessage); message CM_STYLECHANGED; - procedure CMParentDoubleBufferedChange(var Message: TMessage); message CM_PARENTDOUBLEBUFFEREDCHANGED; - - procedure AdjustTotalCount(Node: PVirtualNode; Value: Integer; relative: Boolean = False); - procedure AdjustTotalHeight(Node: PVirtualNode; Value: Integer; relative: Boolean = False); - function CalculateCacheEntryCount: Integer; - procedure CalculateVerticalAlignments(var PaintInfo: TVTPaintInfo; var VButtonAlign: Integer); - function ChangeCheckState(Node: PVirtualNode; Value: TCheckState): Boolean; - function CollectSelectedNodesLTR(MainColumn, NodeLeft, NodeRight: Integer; Alignment: TAlignment; OldRect, - NewRect: TRect): Boolean; - function CollectSelectedNodesRTL(MainColumn, NodeLeft, NodeRight: Integer; Alignment: TAlignment; OldRect, - NewRect: TRect): Boolean; - procedure ClearNodeBackground(const PaintInfo: TVTPaintInfo; UseBackground, Floating: Boolean; R: TRect); - function CompareNodePositions(Node1, Node2: PVirtualNode; ConsiderChildrenAbove: Boolean = False): Integer; - procedure DrawLineImage(const PaintInfo: TVTPaintInfo; X, Y, H, VAlign: Integer; Style: TVTLineType; Reverse: Boolean); - function FindInPositionCache(Node: PVirtualNode; var CurrentPos: Cardinal): PVirtualNode; overload; - function FindInPositionCache(Position: Cardinal; var CurrentPos: Cardinal): PVirtualNode; overload; - procedure FixupTotalCount(Node: PVirtualNode); - procedure FixupTotalHeight(Node: PVirtualNode); - function GetBottomNode: PVirtualNode; - function GetCheckState(Node: PVirtualNode): TCheckState; - function GetCheckType(Node: PVirtualNode): TCheckType; - function GetChildCount(Node: PVirtualNode): Cardinal; - function GetChildrenInitialized(Node: PVirtualNode): Boolean; inline; - function GetCutCopyCount: Integer; - function GetDisabled(Node: PVirtualNode): Boolean; - function GetSyncCheckstateWithSelection(Node: PVirtualNode): Boolean; - function GetDragManager: IVTDragManager; - function GetExpanded(Node: PVirtualNode): Boolean; - function GetFiltered(Node: PVirtualNode): Boolean; - function GetFullyVisible(Node: PVirtualNode): Boolean; - function GetHasChildren(Node: PVirtualNode): Boolean; - function GetMultiline(Node: PVirtualNode): Boolean; - function GetNodeHeight(Node: PVirtualNode): Cardinal; - function GetNodeParent(Node: PVirtualNode): PVirtualNode; - function GetOffsetXY: TPoint; - function GetRootNodeCount: Cardinal; - function GetSelected(Node: PVirtualNode): Boolean; - function GetTopNode: PVirtualNode; - function GetTotalCount: Cardinal; - function GetVerticalAlignment(Node: PVirtualNode): Byte; - function GetVisible(Node: PVirtualNode): Boolean; - function GetVisiblePath(Node: PVirtualNode): Boolean; - function HandleDrawSelection(X, Y: Integer): Boolean; - procedure HandleCheckboxClick(pHitNode: PVirtualNode; pKeys: LongInt); - function HasVisibleNextSibling(Node: PVirtualNode): Boolean; - function HasVisiblePreviousSibling(Node: PVirtualNode): Boolean; - procedure ImageListChange(Sender: TObject); - procedure InitializeFirstColumnValues(var PaintInfo: TVTPaintInfo); - procedure InitRootNode(OldSize: Cardinal = 0); - function IsFirstVisibleChild(Parent, Node: PVirtualNode): Boolean; - function IsLastVisibleChild(Parent, Node: PVirtualNode): Boolean; - function MakeNewNode: PVirtualNode; - function PackArray({*}const TheArray: TNodeArray; Count: Integer): Integer; - procedure FakeReadIdent(Reader: TReader); - procedure SetAlignment(const Value: TAlignment); - procedure SetAnimationDuration(const Value: Cardinal); - procedure SetBackground(const Value: TPicture); - procedure SetBackGroundImageTransparent(const Value: Boolean); - procedure SetBackgroundOffset(const Index, Value: Integer); - procedure SetBorderStyle(Value: TBorderStyle); - procedure SetBottomNode(Node: PVirtualNode); - procedure SetBottomSpace(const Value: Cardinal); - procedure SetButtonFillMode(const Value: TVTButtonFillMode); - procedure SetButtonStyle(const Value: TVTButtonStyle); - procedure SetCheckImageKind(Value: TCheckImageKind); - procedure SetCheckState(Node: PVirtualNode; Value: TCheckState); - procedure SetCheckType(Node: PVirtualNode; Value: TCheckType); - procedure SetClipboardFormats(const Value: TClipboardFormats); - procedure SetColors(const Value: TVTColors); - procedure SetCustomCheckImages(const Value: TCustomImageList); - procedure SetDefaultNodeHeight(Value: Cardinal); - procedure SetDisabled(Node: PVirtualNode; Value: Boolean); - procedure SetEmptyListMessage(const Value: string); - procedure SetExpanded(Node: PVirtualNode; Value: Boolean); - procedure SetFocusedColumn(Value: TColumnIndex); - procedure SetFocusedNode(Value: PVirtualNode); - procedure SetFullyVisible(Node: PVirtualNode; Value: Boolean); - procedure SetHasChildren(Node: PVirtualNode; Value: Boolean); - procedure SetHeader(const Value: TVTHeader); - procedure SetHotNode(Value: PVirtualNode); - procedure SetFiltered(Node: PVirtualNode; Value: Boolean); - procedure SetImages(const Value: TCustomImageList); - procedure SetIndent(Value: Cardinal); - procedure SetLineMode(const Value: TVTLineMode); - procedure SetLineStyle(const Value: TVTLineStyle); - procedure SetMargin(Value: Integer); - procedure SetMultiline(Node: PVirtualNode; const Value: Boolean); - procedure SetNodeAlignment(const Value: TVTNodeAlignment); - procedure SetNodeDataSize(Value: Integer); - procedure SetNodeHeight(Node: PVirtualNode; Value: Cardinal); - procedure SetNodeParent(Node: PVirtualNode; const Value: PVirtualNode); - procedure SetOffsetX(const Value: Integer); - procedure SetOffsetXY(const Value: TPoint); - procedure SetOffsetY(const Value: Integer); - procedure SetOptions(const Value: TCustomVirtualTreeOptions); - procedure SetRootNodeCount(Value: Cardinal); - procedure SetScrollBarOptions(Value: TScrollBarOptions); - procedure SetSearchOption(const Value: TVTIncrementalSearch); - procedure SetSelected(Node: PVirtualNode; Value: Boolean); - procedure SetSelectionCurveRadius(const Value: Cardinal); - procedure SetStateImages(const Value: TCustomImageList); - procedure SetTextMargin(Value: Integer); - procedure SetTopNode(Node: PVirtualNode); - procedure SetUpdateState(Updating: Boolean); - procedure SetVerticalAlignment(Node: PVirtualNode; Value: Byte); - procedure SetVisible(Node: PVirtualNode; Value: Boolean); - procedure SetVisiblePath(Node: PVirtualNode; Value: Boolean); - procedure PrepareBackGroundPicture(Source: TPicture; DrawBitmap: TBitmap; DrawBitmapWidth: Integer; DrawBitMapHeight: Integer; ABkgcolor: TColor); - procedure StaticBackground(Source: TPicture; Target: TCanvas; OffsetPosition: TPoint; R: TRect; aBkgColor: TColor); - procedure StopTimer(ID: Integer); - procedure TileBackground(Source: TPicture; Target: TCanvas; Offset: TPoint; R: TRect; aBkgColor: TColor); - function ToggleCallback(Step, StepSize: Integer; Data: Pointer): Boolean; - - procedure CMColorChange(var Message: TMessage); message CM_COLORCHANGED; - procedure CMCtl3DChanged(var Message: TMessage); message CM_CTL3DCHANGED; - procedure CMBiDiModeChanged(var Message: TMessage); message CM_BIDIMODECHANGED; - procedure CMBorderChanged(var Message: TMessage); message CM_BORDERCHANGED; - procedure CMDenySubclassing(var Message: TMessage); message CM_DENYSUBCLASSING; - procedure CMDrag(var Message: TCMDrag); message CM_DRAG; - procedure CMEnabledChanged(var Message: TMessage); message CM_ENABLEDCHANGED; - procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED; - procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW; - procedure CMHintShowPause(var Message: TCMHintShowPause); message CM_HINTSHOWPAUSE; - procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER; - procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE; - procedure CMMouseWheel(var Message: TCMMouseWheel); message CM_MOUSEWHEEL; - procedure CMSysColorChange(var Message: TMessage); message CM_SYSCOLORCHANGE; - procedure TVMGetItem(var Message: TMessage); message TVM_GETITEM; - procedure TVMGetItemRect(var Message: TMessage); message TVM_GETITEMRECT; - procedure TVMGetNextItem(var Message: TMessage); message TVM_GETNEXTITEM; - procedure WMCancelMode(var Message: TWMCancelMode); message WM_CANCELMODE; - procedure WMChar(var Message: TWMChar); message WM_CHAR; - procedure WMContextMenu(var Message: TWMContextMenu); message WM_CONTEXTMENU; - procedure WMCopy(var Message: TWMCopy); message WM_COPY; - procedure WMCut(var Message: TWMCut); message WM_CUT; - procedure WMEnable(var Message: TWMEnable); message WM_ENABLE; - procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND; - procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE; - procedure WMGetObject(var Message: TMessage); message WM_GETOBJECT; - procedure WMHScroll(var Message: TWMHScroll); message WM_HSCROLL; - procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN; - procedure WMKeyUp(var Message: TWMKeyUp); message WM_KEYUP; - procedure WMKillFocus(var Msg: TWMKillFocus); message WM_KILLFOCUS; - procedure WMLButtonDblClk(var Message: TWMLButtonDblClk); message WM_LBUTTONDBLCLK; - procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN; - procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP; - procedure WMMButtonDblClk(var Message: TWMMButtonDblClk); message WM_MBUTTONDBLCLK; - procedure WMMButtonDown(var Message: TWMMButtonDown); message WM_MBUTTONDOWN; - procedure WMMButtonUp(var Message: TWMMButtonUp); message WM_MBUTTONUP; - procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE; - procedure WMNCDestroy(var Message: TWMNCDestroy); message WM_NCDESTROY; - procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST; - procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT; - procedure WMPaint(var Message: TWMPaint); message WM_PAINT; - procedure WMPaste(var Message: TWMPaste); message WM_PASTE; - procedure WMPrint(var Message: TWMPrint); message WM_PRINT; - procedure WMPrintClient(var Message: TWMPrintClient); message WM_PRINTCLIENT; - procedure WMRButtonDblClk(var Message: TWMRButtonDblClk); message WM_RBUTTONDBLCLK; - procedure WMRButtonDown(var Message: TWMRButtonDown); message WM_RBUTTONDOWN; - procedure WMRButtonUp(var Message: TWMRButtonUp); message WM_RBUTTONUP; - procedure WMSetCursor(var Message: TWMSetCursor); message WM_SETCURSOR; - procedure WMSetFocus(var Msg: TWMSetFocus); message WM_SETFOCUS; - procedure WMSize(var Message: TWMSize); message WM_SIZE; - procedure WMTimer(var Message: TWMTimer); message WM_TIMER; - procedure WMThemeChanged(var Message: TMessage); message WM_THEMECHANGED; - procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL; - function GetRangeX: Cardinal; - function GetDoubleBuffered: Boolean; - procedure SetDoubleBuffered(const Value: Boolean); - function GetVclStyleEnabled: Boolean; inline; - procedure SetOnPrepareButtonImages(const Value: TVTPrepareButtonImagesEvent); - - protected - FFontChanged: Boolean; // flag for keeping informed about font changes in the off screen buffer // [IPK] - private to protected - procedure AutoScale(isDpiChange: Boolean); virtual; - procedure AddToSelection(Node: PVirtualNode; NotifySynced: Boolean); overload; virtual; - procedure AddToSelection(const NewItems: TNodeArray; NewLength: Integer; ForceInsert: Boolean = False); overload; virtual; - procedure AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; var NextNonEmpty: TColumnIndex); virtual; - procedure AdjustPanningCursor(X, Y: Integer); virtual; - procedure AdviseChangeEvent(StructureChange: Boolean; Node: PVirtualNode; Reason: TChangeReason); virtual; - function AllocateInternalDataArea(Size: Cardinal): Cardinal; virtual; - procedure Animate(Steps, Duration: Cardinal; Callback: TVTAnimationCallback; Data: Pointer); virtual; - function CalculateSelectionRect(X, Y: Integer): Boolean; virtual; - function CanAutoScroll: Boolean; virtual; - function CanShowDragImage: Boolean; virtual; - function CanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex): Boolean; - procedure Change(Node: PVirtualNode); virtual; - procedure ChangeTreeStatesAsync(EnterStates, LeaveStates: TVirtualTreeStates); - procedure ChangeScale(M, D: Integer{$if CompilerVersion >= 31}; isDpiChange: Boolean{$ifend}); override; - function CheckParentCheckState(Node: PVirtualNode; NewCheckState: TCheckState): Boolean; virtual; - procedure ClearDragManager; - procedure ClearSelection(pFireChangeEvent: Boolean); overload; virtual; - procedure ClearTempCache; virtual; - function ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; - function ComputeRTLOffset(ExcludeScrollBar: Boolean = False): Integer; virtual; - function CountLevelDifference(Node1, Node2: PVirtualNode): Integer; virtual; - function CountVisibleChildren(Node: PVirtualNode): Cardinal; virtual; - procedure CreateParams(var Params: TCreateParams); override; - procedure CreateWnd; override; - procedure DefineProperties(Filer: TFiler); override; - procedure DeleteNode(Node: PVirtualNode; Reindex: Boolean; ParentClearing: Boolean); overload; - function DetermineDropMode(const P: TPoint; var HitInfo: THitInfo; var NodeRect: TRect): TDropMode; virtual; - procedure DetermineHiddenChildrenFlag(Node: PVirtualNode); virtual; - procedure DetermineHiddenChildrenFlagAllNodes; virtual; - procedure DetermineHitPositionLTR(var HitInfo: THitInfo; Offset, Right: Integer; Alignment: TAlignment); virtual; - procedure DetermineHitPositionRTL(var HitInfo: THitInfo; Offset, Right: Integer; Alignment: TAlignment); virtual; - function DetermineLineImageAndSelectLevel(Node: PVirtualNode; var LineImage: TLineImage): Integer; virtual; - function DetermineNextCheckState(CheckType: TCheckType; CheckState: TCheckState): TCheckState; virtual; - function DetermineScrollDirections(X, Y: Integer): TScrollDirections; virtual; - procedure DoAdvancedHeaderDraw(var PaintInfo: THeaderPaintInfo; const Elements: THeaderPaintElements); virtual; - procedure DoAfterCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect); virtual; - procedure DoAfterItemErase(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect); virtual; - procedure DoAfterItemPaint(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect); virtual; - procedure DoAfterPaint(Canvas: TCanvas); virtual; - procedure DoAutoScroll(X, Y: Integer); virtual; - function DoBeforeDrag(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; - procedure DoBeforeCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; - CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); virtual; - procedure DoBeforeItemErase(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect; var Color: TColor; - var EraseAction: TItemEraseAction); virtual; - function DoBeforeItemPaint(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect): Boolean; virtual; - procedure DoBeforePaint(Canvas: TCanvas); virtual; - function DoCancelEdit: Boolean; virtual; - procedure DoCanEdit(Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); virtual; - procedure DoCanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex; - var Allowed: Boolean); virtual; - procedure DoChange(Node: PVirtualNode); virtual; - procedure DoCheckClick(Node: PVirtualNode; NewCheckState: TCheckState); virtual; - procedure DoChecked(Node: PVirtualNode); virtual; - function DoChecking(Node: PVirtualNode; var NewCheckState: TCheckState): Boolean; virtual; - procedure DoCollapsed(Node: PVirtualNode); virtual; - function DoCollapsing(Node: PVirtualNode): Boolean; virtual; - procedure DoColumnClick(Column: TColumnIndex; Shift: TShiftState); virtual; - procedure DoColumnDblClick(Column: TColumnIndex; Shift: TShiftState); virtual; - procedure DoColumnResize(Column: TColumnIndex); virtual; - procedure DoColumnVisibilityChanged(const Column: TColumnIndex; Visible: Boolean); virtual; - function DoCompare(Node1, Node2: PVirtualNode; Column: TColumnIndex): Integer; virtual; - function DoCreateDataObject: IDataObject; virtual; - function DoCreateDragManager: IVTDragManager; virtual; - function DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; virtual; - procedure DoDragging(P: TPoint); virtual; - procedure DoDragExpand; virtual; - procedure DoBeforeDrawLineImage(Node: PVirtualNode; Level: Integer; var XPos: Integer); virtual; - function DoDragOver(Source: TObject; Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode; - var Effect: Integer): Boolean; virtual; - procedure DoDragDrop(Source: TObject; const DataObject: IDataObject; const Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; - var Effect: Integer; Mode: TDropMode); virtual; - procedure DoDrawHint(Canvas: TCanvas; Node: PVirtualNode; R: TRect; Column: - TColumnIndex); - procedure DoEdit; virtual; - procedure DoEndDrag(Target: TObject; X, Y: Integer); override; - function DoEndEdit: Boolean; virtual; - procedure DoEndOperation(OperationKind: TVTOperationKind); virtual; - procedure DoEnter(); override; - procedure DoExpanded(Node: PVirtualNode); virtual; - function DoExpanding(Node: PVirtualNode): Boolean; virtual; - procedure DoFocusChange(Node: PVirtualNode; Column: TColumnIndex); virtual; - function DoFocusChanging(OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex): Boolean; virtual; - procedure DoFocusNode(Node: PVirtualNode; Ask: Boolean); virtual; - procedure DoFreeNode(Node: PVirtualNode); virtual; - function DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex; - CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; virtual; - procedure DoGetCursor(var Cursor: TCursor); virtual; - procedure DoGetHeaderCursor(var Cursor: HCURSOR); virtual; - procedure DoGetHintSize(Node: PVirtualNode; Column: TColumnIndex; var R: - TRect); virtual; - procedure DoGetHintKind(Node: PVirtualNode; Column: TColumnIndex; var Kind: - TVTHintKind); - function DoGetImageIndex(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; - var Ghosted: Boolean; var Index: TImageIndex): TCustomImageList; virtual; - procedure DoGetImageText(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var ImageText: string); virtual; - procedure DoGetLineStyle(var Bits: Pointer); virtual; - function DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): string; virtual; - function DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): string; virtual; - function DoGetNodeExtraWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; virtual; - function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; virtual; - function DoGetPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Position: TPoint): TPopupMenu; virtual; - procedure DoGetUserClipboardFormats(var Formats: TFormatEtcArray); virtual; - procedure DoHeaderAddPopupItem(const Column: TColumnIndex; var Cmd: TAddPopupItemType); - procedure DoHeaderClick(const HitInfo: TVTHeaderHitInfo); virtual; - procedure DoHeaderDblClick(const HitInfo: TVTHeaderHitInfo); virtual; - procedure DoHeaderDragged(Column: TColumnIndex; OldPosition: TColumnPosition); virtual; - procedure DoHeaderDraggedOut(Column: TColumnIndex; DropPosition: TPoint); virtual; - function DoHeaderDragging(Column: TColumnIndex): Boolean; virtual; - procedure DoHeaderDraw(Canvas: TCanvas; Column: TVirtualTreeColumn; R: TRect; Hover, Pressed: Boolean; - DropMark: TVTDropMarkMode); virtual; - procedure DoHeaderDrawQueryElements(var PaintInfo: THeaderPaintInfo; var Elements: THeaderPaintElements); virtual; - procedure DoHeaderMouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual; - procedure DoHeaderMouseMove(Shift: TShiftState; X, Y: Integer); virtual; - procedure DoHeaderMouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); virtual; - procedure DoHotChange(Old, New: PVirtualNode); virtual; - function DoIncrementalSearch(Node: PVirtualNode; const Text: string): Integer; virtual; - function DoInitChildren(Node: PVirtualNode; var ChildCount: Cardinal): Boolean; virtual; - procedure DoInitNode(Parent, Node: PVirtualNode; var InitStates: TVirtualNodeInitStates); virtual; - function DoKeyAction(var CharCode: Word; var Shift: TShiftState): Boolean; virtual; - procedure DoLoadUserData(Node: PVirtualNode; Stream: TStream); virtual; - procedure DoMeasureItem(TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer); virtual; - procedure DoMouseEnter(); virtual; - procedure DoMouseLeave(); virtual; - procedure DoNodeCopied(Node: PVirtualNode); virtual; - function DoNodeCopying(Node, NewParent: PVirtualNode): Boolean; virtual; - procedure DoNodeClick(const HitInfo: THitInfo); virtual; - procedure DoNodeDblClick(const HitInfo: THitInfo); virtual; - function DoNodeHeightDblClickResize(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState; - P: TPoint): Boolean; virtual; - function DoNodeHeightTracking(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState; - var TrackPoint: TPoint; P: TPoint): Boolean; virtual; - procedure DoNodeMoved(Node: PVirtualNode); virtual; - function DoNodeMoving(Node, NewParent: PVirtualNode): Boolean; virtual; - function DoPaintBackground(Canvas: TCanvas; R: TRect): Boolean; virtual; - procedure DoPaintDropMark(Canvas: TCanvas; Node: PVirtualNode; R: TRect); virtual; - procedure DoPaintNode(var PaintInfo: TVTPaintInfo); virtual; - procedure DoPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Position: TPoint); virtual; - procedure DoRemoveFromSelection(Node: PVirtualNode); virtual; - function DoRenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; - ForClipboard: Boolean): HRESULT; virtual; - procedure DoReset(Node: PVirtualNode); virtual; - procedure DoSaveUserData(Node: PVirtualNode; Stream: TStream); virtual; - procedure DoScroll(DeltaX, DeltaY: Integer); virtual; - function DoSetOffsetXY(Value: TPoint; Options: TScrollUpdateOptions; ClipRect: PRect = nil): Boolean; virtual; - procedure DoShowScrollBar(Bar: Integer; Show: Boolean); virtual; - procedure DoStartDrag(var DragObject: TDragObject); override; - procedure DoStartOperation(OperationKind: TVTOperationKind); virtual; - procedure DoStateChange(Enter: TVirtualTreeStates; Leave: TVirtualTreeStates = []); virtual; - procedure DoStructureChange(Node: PVirtualNode; Reason: TChangeReason); virtual; - procedure DoTimerScroll; virtual; - procedure DoUpdating(State: TVTUpdateState); virtual; - function DoValidateCache: Boolean; virtual; - procedure DragAndDrop(AllowedEffects: DWord; const DataObject: IDataObject; var DragEffect: Integer); virtual; - procedure DragCanceled; override; - function DragDrop(const DataObject: IDataObject; KeyState: Integer; Pt: TPoint; - var Effect: Integer): HResult; reintroduce; virtual; - function DragEnter(KeyState: Integer; Pt: TPoint; var Effect: Integer): HResult; virtual; - procedure DragFinished; virtual; - procedure DragLeave; virtual; - function DragOver(Source: TObject; KeyState: Integer; DragState: TDragState; Pt: TPoint; - var Effect: Integer): HResult; reintroduce; virtual; - procedure DrawDottedHLine(const PaintInfo: TVTPaintInfo; Left, Right, Top: Integer); virtual; - procedure DrawDottedVLine(const PaintInfo: TVTPaintInfo; Top, Bottom, Left: Integer; UseSelectedBkColor: Boolean = False); virtual; - procedure EndOperation(OperationKind: TVTOperationKind); - procedure EnsureNodeFocused(); virtual; - function FindNodeInSelection(P: PVirtualNode; var Index: Integer; LowBound, HighBound: Integer): Boolean; virtual; - procedure FinishChunkHeader(Stream: TStream; StartPos, EndPos: Integer); virtual; - procedure FontChanged(AFont: TObject); virtual; - function GetBorderDimensions: TSize; virtual; - function GetCheckedCount: Integer; - function GetCheckImage(Node: PVirtualNode; ImgCheckType: TCheckType = ctNone; - ImgCheckState: TCheckState = csUncheckedNormal; ImgEnabled: Boolean = True): Integer; virtual; - function GetColumnClass: TVirtualTreeColumnClass; virtual; - function GetDefaultHintKind: TVTHintKind; virtual; - function GetHeaderClass: TVTHeaderClass; virtual; - function GetHintWindowClass: THintWindowClass; virtual; - procedure GetImageIndex(var Info: TVTPaintInfo; Kind: TVTImageKind; InfoIndex: TVTImageInfoIndex); virtual; - function GetImageSize(Node: PVirtualNode; Kind: TVTImageKind = TVTImageKind.ikNormal; Column: TColumnIndex = 0; IncludePadding: Boolean = True): TSize; virtual; - function GetNodeImageSize(Node: PVirtualNode): TSize; virtual; deprecated 'Use GetImageSize instead'; - function GetMaxRightExtend: Cardinal; virtual; - procedure GetNativeClipboardFormats(var Formats: TFormatEtcArray); virtual; - function GetOperationCanceled: Boolean; - function GetOptionsClass: TTreeOptionsClass; virtual; - function GetTreeFromDataObject(const DataObject: IDataObject): TBaseVirtualTree; virtual; - procedure HandleHotTrack(X, Y: Integer); virtual; - procedure HandleIncrementalSearch(CharCode: Word); virtual; - procedure HandleMouseDblClick(var Message: TWMMouse; const HitInfo: THitInfo); virtual; - procedure HandleMouseDown(var Message: TWMMouse; var HitInfo: THitInfo); virtual; - procedure HandleMouseUp(var Message: TWMMouse; const HitInfo: THitInfo); virtual; - procedure HandleClickSelection(LastFocused, NewNode: PVirtualNode; Shift: TShiftState; DragPending: Boolean); - function HasImage(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex): Boolean; virtual; deprecated 'Use GetImageSize instead'; - function HasPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Pos: TPoint): Boolean; virtual; - procedure InitChildren(Node: PVirtualNode); virtual; - procedure InitNode(Node: PVirtualNode); virtual; - procedure InternalAddFromStream(Stream: TStream; Version: Integer; Node: PVirtualNode); virtual; - function InternalAddToSelection(Node: PVirtualNode; ForceInsert: Boolean): Boolean; overload; - function InternalAddToSelection(const NewItems: TNodeArray; NewLength: Integer; - ForceInsert: Boolean): Boolean; overload; - procedure InternalCacheNode(Node: PVirtualNode); virtual; - procedure InternalClearSelection; virtual; - procedure InternalConnectNode(Node, Destination: PVirtualNode; Target: TBaseVirtualTree; Mode: TVTNodeAttachMode); virtual; - function InternalData(Node: PVirtualNode): Pointer; - procedure InternalDisconnectNode(Node: PVirtualNode; KeepFocus: Boolean; Reindex: Boolean = True; ParentClearing: Boolean = False); virtual; - procedure InternalRemoveFromSelection(Node: PVirtualNode); virtual; - procedure InterruptValidation(pWaitForValidationTermination: Boolean = True); - procedure InvalidateCache; - procedure Loaded; override; - procedure MainColumnChanged; virtual; - procedure MarkCutCopyNodes; virtual; - procedure MouseMove(Shift: TShiftState; X, Y: Integer); override; - procedure Notification(AComponent: TComponent; Operation: TOperation); override; - procedure OriginalWMNCPaint(DC: HDC); virtual; - procedure Paint; override; - procedure PaintCheckImage(Canvas: TCanvas; const ImageInfo: TVTImageInfo; Selected: Boolean); virtual; - procedure PaintImage(var PaintInfo: TVTPaintInfo; ImageInfoIndex: TVTImageInfoIndex; DoOverlay: Boolean); virtual; - procedure PaintNodeButton(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const R: TRect; ButtonX, - ButtonY: Integer; BidiMode: TBiDiMode); virtual; - procedure PaintTreeLines(const PaintInfo: TVTPaintInfo; IndentSize: Integer; const LineImage: TLineImage); virtual; - procedure PaintSelectionRectangle(Target: TCanvas; WindowOrgX: Integer; const SelectionRect: TRect; - TargetRect: TRect); virtual; - procedure PanningWindowProc(var Message: TMessage); virtual; - procedure PrepareBitmaps(NeedButtons, NeedLines: Boolean); - procedure PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer); virtual; - function ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType, - ChunkSize: Integer): Boolean; virtual; - procedure ReadNode(Stream: TStream; Version: Integer; Node: PVirtualNode); virtual; - procedure RedirectFontChangeEvent(Canvas: TCanvas); virtual; - procedure RemoveFromSelection(Node: PVirtualNode); virtual; - procedure UpdateNextNodeToSelect(Node: PVirtualNode); virtual; - function RenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; ForClipboard: Boolean): HResult; virtual; - procedure ResetRangeAnchor; virtual; - procedure RestoreFontChangeEvent(Canvas: TCanvas); virtual; - procedure SelectNodes(StartNode, EndNode: PVirtualNode; AddOnly: Boolean); virtual; - procedure SetChildCount(Node: PVirtualNode; NewChildCount: Cardinal); virtual; - procedure SetFocusedNodeAndColumn(Node: PVirtualNode; Column: TColumnIndex); virtual; - procedure SetRangeX(value: Cardinal); - procedure SetWindowTheme(const Theme: string); - procedure SetVisibleCount(value : Cardinal); - procedure SkipNode(Stream: TStream); virtual; - procedure StartOperation(OperationKind: TVTOperationKind); - procedure StartWheelPanning(Position: TPoint); virtual; - procedure StopWheelPanning; virtual; - procedure StructureChange(Node: PVirtualNode; Reason: TChangeReason); virtual; - function SuggestDropEffect(Source: TObject; Shift: TShiftState; Pt: TPoint; AllowedEffects: Integer): Integer; virtual; - procedure ToggleSelection(StartNode, EndNode: PVirtualNode); virtual; - procedure TrySetFocus(); - procedure UnselectNodes(StartNode, EndNode: PVirtualNode); virtual; - procedure UpdateColumnCheckState(Col: TVirtualTreeColumn); - procedure UpdateDesigner; virtual; - procedure UpdateEditBounds; virtual; - procedure UpdateHeaderRect; virtual; - procedure UpdateStyleElements; override; - procedure UpdateWindowAndDragImage(const Tree: TBaseVirtualTree; TreeRect: TRect; UpdateNCArea, - ReshowDragImage: Boolean); virtual; - procedure ValidateCache; virtual; - procedure ValidateNodeDataSize(var Size: Integer); virtual; - procedure WndProc(var Message: TMessage); override; - procedure WriteChunks(Stream: TStream; Node: PVirtualNode); virtual; - procedure WriteNode(Stream: TStream; Node: PVirtualNode); virtual; - - procedure VclStyleChanged; virtual; - property VclStyleEnabled: Boolean read GetVclStyleEnabled; - property TotalInternalDataSize: Cardinal read FTotalInternalDataSize; - // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) - function StyleServices(AControl: TControl = nil): TCustomStyleServices; - property Alignment: TAlignment read FAlignment write SetAlignment default taLeftJustify; - property AnimationDuration: Cardinal read FAnimationDuration write SetAnimationDuration default 200; - property AutoExpandDelay: Cardinal read FAutoExpandDelay write FAutoExpandDelay default 1000; - property AutoScrollDelay: Cardinal read FAutoScrollDelay write FAutoScrollDelay default 1000; - property AutoScrollInterval: TAutoScrollInterval read FAutoScrollInterval write FAutoScrollInterval default 1; - property Background: TPicture read FBackground write SetBackground; - property BackGroundImageTransparent: Boolean read FBackGroundImageTransparent write SetBackGroundImageTransparent default False; - property BackgroundOffsetX: Integer index 0 read FBackgroundOffsetX write SetBackgroundOffset default 0; - property BackgroundOffsetY: Integer index 1 read FBackgroundOffsetY write SetBackgroundOffset default 0; - property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle default bsSingle; - property BottomSpace: Cardinal read FBottomSpace write SetBottomSpace default 0; - property ButtonFillMode: TVTButtonFillMode read FButtonFillMode write SetButtonFillMode default fmTreeColor; - property ButtonStyle: TVTButtonStyle read FButtonStyle write SetButtonStyle default bsRectangle; - property ChangeDelay: Cardinal read FChangeDelay write FChangeDelay default 0; - property CheckImageKind: TCheckImageKind read FCheckImageKind write SetCheckImageKind stored False default ckSystemDefault; // deprecated, see issue #622 - property ClipboardFormats: TClipboardFormats read FClipboardFormats write SetClipboardFormats; - property Colors: TVTColors read FColors write SetColors; - property CustomCheckImages: TCustomImageList read FCustomCheckImages write SetCustomCheckImages; - property DefaultHintKind: TVTHintKind read GetDefaultHintKind; - property DefaultNodeHeight: Cardinal read FDefaultNodeHeight write SetDefaultNodeHeight default 18; - property DefaultPasteMode: TVTNodeAttachMode read FDefaultPasteMode write FDefaultPasteMode default amAddChildLast; - property DragHeight: Integer read FDragHeight write FDragHeight default 350; - property DragImageKind: TVTDragImageKind read FDragImageKind write FDragImageKind default diComplete; - property DragOperations: TDragOperations read FDragOperations write FDragOperations default [doCopy, doMove]; - property DragSelection: TNodeArray read FDragSelection; - property LastDragEffect: Integer read FLastDragEffect; - property DragType: TVTDragType read FDragType write FDragType default dtOLE; - property DragWidth: Integer read FDragWidth write FDragWidth default 200; - property DrawSelectionMode: TVTDrawSelectionMode read FDrawSelectionMode write FDrawSelectionMode - default smDottedRectangle; - property EditColumn: TColumnIndex read FEditColumn write FEditColumn; - property EditDelay: Cardinal read FEditDelay write FEditDelay default 1000; - property EffectiveOffsetX: Integer read FEffectiveOffsetX; - property HeaderRect: TRect read FHeaderRect; - property HintMode: TVTHintMode read FHintMode write FHintMode default hmDefault; - property HintData: TVTHintData read FHintData write FHintData; - property HotCursor: TCursor read FHotCursor write FHotCursor default crDefault; - property Images: TCustomImageList read FImages write SetImages; - property IncrementalSearch: TVTIncrementalSearch read FIncrementalSearch write SetSearchOption default isNone; - property IncrementalSearchDirection: TVTSearchDirection read FSearchDirection write FSearchDirection default sdForward; - property IncrementalSearchStart: TVTSearchStart read FSearchStart write FSearchStart default ssFocusedNode; - property IncrementalSearchTimeout: Cardinal read FSearchTimeout write FSearchTimeout default 1000; - property Indent: Cardinal read FIndent write SetIndent default 18; - property LastClickPos: TPoint read FLastClickPos write FLastClickPos; - property LastDropMode: TDropMode read FLastDropMode write FLastDropMode; - property LastHintRect: TRect read FLastHintRect write FLastHintRect; - property LineMode: TVTLineMode read FLineMode write SetLineMode default lmNormal; - property LineStyle: TVTLineStyle read FLineStyle write SetLineStyle default lsDotted; - property Margin: Integer read FMargin write SetMargin default 4; - property NextNodeToSelect: PVirtualNode read FNextNodeToSelect; // Next tree node that we would like to select if the current one gets deleted - property NodeAlignment: TVTNodeAlignment read FNodeAlignment write SetNodeAlignment default naProportional; - property NodeDataSize: Integer read FNodeDataSize write SetNodeDataSize default -1; - property OperationCanceled: Boolean read GetOperationCanceled; - property HotMinusBM: TBitmap read FHotMinusBM; - property HotPlusBM: TBitmap read FHotPlusBM; - property MinusBM: TBitmap read FMinusBM; - property PlusBM: TBitmap read FPlusBM; - property RangeX: Cardinal read GetRangeX;// Returns the width of the virtual tree in pixels, (not ClientWidth). If there are columns it returns the total width of all of them; otherwise it returns the maximum of the all the line's data widths. - property RangeY: Cardinal read FRangeY; - property RootNodeCount: Cardinal read GetRootNodeCount write SetRootNodeCount default 0; - property ScrollBarOptions: TScrollBarOptions read FScrollBarOptions write SetScrollBarOptions; - property SelectionBlendFactor: Byte read FSelectionBlendFactor write FSelectionBlendFactor default 128; - property SelectionCurveRadius: Cardinal read FSelectionCurveRadius write SetSelectionCurveRadius default 0; - property StateImages: TCustomImageList read FStateImages write SetStateImages; - property TextMargin: Integer read FTextMargin write SetTextMargin default 4; - property TreeOptions: TCustomVirtualTreeOptions read FOptions write SetOptions; - property WantTabs: Boolean read FWantTabs write FWantTabs default False; - property SyncCheckstateWithSelection[Node: PVirtualNode]: Boolean read GetSyncCheckstateWithSelection; - - property OnAddToSelection: TVTAddToSelectionEvent read FOnAddToSelection write FOnAddToSelection; - property OnAdvancedHeaderDraw: TVTAdvancedHeaderPaintEvent read FOnAdvancedHeaderDraw write FOnAdvancedHeaderDraw; - property OnAfterAutoFitColumn: TVTAfterAutoFitColumnEvent read FOnAfterAutoFitColumn write FOnAfterAutoFitColumn; - property OnAfterAutoFitColumns: TVTAfterAutoFitColumnsEvent read FOnAfterAutoFitColumns write FOnAfterAutoFitColumns; - property OnAfterCellPaint: TVTAfterCellPaintEvent read FOnAfterCellPaint write FOnAfterCellPaint; - property OnAfterColumnExport : TVTColumnExportEvent read FOnAfterColumnExport write FOnAfterColumnExport; - property OnAfterColumnWidthTracking: TVTAfterColumnWidthTrackingEvent read FOnAfterColumnWidthTracking write FOnAfterColumnWidthTracking; - property OnAfterGetMaxColumnWidth: TVTAfterGetMaxColumnWidthEvent read FOnAfterGetMaxColumnWidth write FOnAfterGetMaxColumnWidth; - property OnAfterHeaderExport: TVTTreeExportEvent read FOnAfterHeaderExport write FOnAfterHeaderExport; - property OnAfterHeaderHeightTracking: TVTAfterHeaderHeightTrackingEvent read FOnAfterHeaderHeightTracking - write FOnAfterHeaderHeightTracking; - property OnAfterItemErase: TVTAfterItemEraseEvent read FOnAfterItemErase write FOnAfterItemErase; - property OnAfterItemPaint: TVTAfterItemPaintEvent read FOnAfterItemPaint write FOnAfterItemPaint; - property OnAfterNodeExport: TVTNodeExportEvent read FOnAfterNodeExport write FOnAfterNodeExport; - property OnAfterPaint: TVTPaintEvent read FOnAfterPaint write FOnAfterPaint; - property OnAfterTreeExport: TVTTreeExportEvent read FOnAfterTreeExport write FOnAfterTreeExport; - property OnBeforeAutoFitColumn: TVTBeforeAutoFitColumnEvent read FOnBeforeAutoFitColumn write FOnBeforeAutoFitColumn; - property OnBeforeAutoFitColumns: TVTBeforeAutoFitColumnsEvent read FOnBeforeAutoFitColumns write FOnBeforeAutoFitColumns; - property OnBeforeCellPaint: TVTBeforeCellPaintEvent read FOnBeforeCellPaint write FOnBeforeCellPaint; - property OnBeforeColumnExport: TVTColumnExportEvent read FOnBeforeColumnExport write FOnBeforeColumnExport; - property OnBeforeColumnWidthTracking: TVTBeforeColumnWidthTrackingEvent read FOnBeforeColumnWidthTracking - write FOnBeforeColumnWidthTracking; - property OnBeforeDrawTreeLine: TVTBeforeDrawLineImageEvent read FOnBeforeDrawLineImage write FOnBeforeDrawLineImage; - property OnBeforeGetMaxColumnWidth: TVTBeforeGetMaxColumnWidthEvent read FOnBeforeGetMaxColumnWidth write FOnBeforeGetMaxColumnWidth; - property OnBeforeHeaderExport: TVTTreeExportEvent read FOnBeforeHeaderExport write FOnBeforeHeaderExport; - property OnBeforeHeaderHeightTracking: TVTBeforeHeaderHeightTrackingEvent read FOnBeforeHeaderHeightTracking - write FOnBeforeHeaderHeightTracking; - property OnBeforeItemErase: TVTBeforeItemEraseEvent read FOnBeforeItemErase write FOnBeforeItemErase; - property OnBeforeItemPaint: TVTBeforeItemPaintEvent read FOnBeforeItemPaint write FOnBeforeItemPaint; - property OnBeforeNodeExport: TVTNodeExportEvent read FOnBeforeNodeExport write FOnBeforeNodeExport; - property OnBeforePaint: TVTPaintEvent read FOnBeforePaint write FOnBeforePaint; - property OnBeforeTreeExport: TVTTreeExportEvent read FOnBeforeTreeExport write FOnBeforeTreeExport; - property OnCanSplitterResizeColumn: TVTCanSplitterResizeColumnEvent read FOnCanSplitterResizeColumn write FOnCanSplitterResizeColumn; - property OnCanSplitterResizeHeader: TVTCanSplitterResizeHeaderEvent read FOnCanSplitterResizeHeader write FOnCanSplitterResizeHeader; - property OnCanSplitterResizeNode: TVTCanSplitterResizeNodeEvent read FOnCanSplitterResizeNode write FOnCanSplitterResizeNode; - property OnChange: TVTChangeEvent read FOnChange write FOnChange; - property OnChecked: TVTChangeEvent read FOnChecked write FOnChecked; - property OnChecking: TVTCheckChangingEvent read FOnChecking write FOnChecking; - property OnCollapsed: TVTChangeEvent read FOnCollapsed write FOnCollapsed; - property OnCollapsing: TVTChangingEvent read FOnCollapsing write FOnCollapsing; - property OnColumnClick: TVTColumnClickEvent read FOnColumnClick write FOnColumnClick; - property OnColumnDblClick: TVTColumnDblClickEvent read FOnColumnDblClick write FOnColumnDblClick; - property OnColumnExport : TVTColumnExportEvent read FOnColumnExport write FOnColumnExport; - property OnColumnResize: TVTHeaderNotifyEvent read FOnColumnResize write FOnColumnResize; - property OnColumnVisibilityChanged: TColumnChangeEvent read fOnColumnVisibilityChanged write fOnColumnVisibilityChanged; - property OnColumnWidthDblClickResize: TVTColumnWidthDblClickResizeEvent read FOnColumnWidthDblClickResize - write FOnColumnWidthDblClickResize; - property OnColumnWidthTracking: TVTColumnWidthTrackingEvent read FOnColumnWidthTracking write FOnColumnWidthTracking; - property OnCompareNodes: TVTCompareEvent read FOnCompareNodes write FOnCompareNodes; - property OnCreateDataObject: TVTCreateDataObjectEvent read FOnCreateDataObject write FOnCreateDataObject; - property OnCreateDragManager: TVTCreateDragManagerEvent read FOnCreateDragManager write FOnCreateDragManager; - property OnCreateEditor: TVTCreateEditorEvent read FOnCreateEditor write FOnCreateEditor; - property OnDragAllowed: TVTDragAllowedEvent read FOnDragAllowed write FOnDragAllowed; - property OnDragOver: TVTDragOverEvent read FOnDragOver write FOnDragOver; - property OnDragDrop: TVTDragDropEvent read FOnDragDrop write FOnDragDrop; - property OnDrawHint: TVTDrawHintEvent read FOnDrawHint write FOnDrawHint; - property OnEditCancelled: TVTEditCancelEvent read FOnEditCancelled write FOnEditCancelled; - property OnEditing: TVTEditChangingEvent read FOnEditing write FOnEditing; - property OnEdited: TVTEditChangeEvent read FOnEdited write FOnEdited; - property OnEndOperation: TVTOperationEvent read FOnEndOperation write FOnEndOperation; - property OnExpanded: TVTChangeEvent read FOnExpanded write FOnExpanded; - property OnExpanding: TVTChangingEvent read FOnExpanding write FOnExpanding; - property OnFocusChanged: TVTFocusChangeEvent read FOnFocusChanged write FOnFocusChanged; - property OnFocusChanging: TVTFocusChangingEvent read FOnFocusChanging write FOnFocusChanging; - property OnFreeNode: TVTFreeNodeEvent read FOnFreeNode write FOnFreeNode; - property OnGetCellIsEmpty: TVTGetCellIsEmptyEvent read FOnGetCellIsEmpty write FOnGetCellIsEmpty; - property OnGetCursor: TVTGetCursorEvent read FOnGetCursor write FOnGetCursor; - property OnGetHeaderCursor: TVTGetHeaderCursorEvent read FOnGetHeaderCursor write FOnGetHeaderCursor; - property OnGetHelpContext: TVTHelpContextEvent read FOnGetHelpContext write FOnGetHelpContext; - property OnGetHintSize: TVTGetHintSizeEvent read FOnGetHintSize write - FOnGetHintSize; - property OnGetHintKind: TVTHintKindEvent read FOnGetHintKind write - FOnGetHintKind; - property OnGetImageIndex: TVTGetImageEvent read FOnGetImage write FOnGetImage; - property OnGetImageIndexEx: TVTGetImageExEvent read FOnGetImageEx write FOnGetImageEx; - property OnGetImageText: TVTGetImageTextEvent read FOnGetImageText write FOnGetImageText; - property OnGetLineStyle: TVTGetLineStyleEvent read FOnGetLineStyle write FOnGetLineStyle; - property OnGetNodeDataSize: TVTGetNodeDataSizeEvent read FOnGetNodeDataSize write FOnGetNodeDataSize; - property OnGetPopupMenu: TVTPopupEvent read FOnGetPopupMenu write FOnGetPopupMenu; - property OnGetUserClipboardFormats: TVTGetUserClipboardFormatsEvent read FOnGetUserClipboardFormats - write FOnGetUserClipboardFormats; - property OnHeaderAddPopupItem: TVTHeaderAddPopupItemEvent read FOnHeaderAddPopupItem write FOnHeaderAddPopupItem; - property OnHeaderClick: TVTHeaderClickEvent read FOnHeaderClick write FOnHeaderClick; - property OnHeaderDblClick: TVTHeaderClickEvent read FOnHeaderDblClick write FOnHeaderDblClick; - property OnHeaderDragged: TVTHeaderDraggedEvent read FOnHeaderDragged write FOnHeaderDragged; - property OnHeaderDraggedOut: TVTHeaderDraggedOutEvent read FOnHeaderDraggedOut write FOnHeaderDraggedOut; - property OnHeaderDragging: TVTHeaderDraggingEvent read FOnHeaderDragging write FOnHeaderDragging; - property OnHeaderDraw: TVTHeaderPaintEvent read FOnHeaderDraw write FOnHeaderDraw; - property OnHeaderDrawQueryElements: TVTHeaderPaintQueryElementsEvent read FOnHeaderDrawQueryElements - write FOnHeaderDrawQueryElements; - property OnHeaderHeightTracking: TVTHeaderHeightTrackingEvent read FOnHeaderHeightTracking - write FOnHeaderHeightTracking; - property OnHeaderHeightDblClickResize: TVTHeaderHeightDblClickResizeEvent read FOnHeaderHeightDblClickResize - write FOnHeaderHeightDblClickResize; - property OnHeaderMouseDown: TVTHeaderMouseEvent read FOnHeaderMouseDown write FOnHeaderMouseDown; - property OnHeaderMouseMove: TVTHeaderMouseMoveEvent read FOnHeaderMouseMove write FOnHeaderMouseMove; - property OnHeaderMouseUp: TVTHeaderMouseEvent read FOnHeaderMouseUp write FOnHeaderMouseUp; - property OnHotChange: TVTHotNodeChangeEvent read FOnHotChange write FOnHotChange; - property OnIncrementalSearch: TVTIncrementalSearchEvent read FOnIncrementalSearch write FOnIncrementalSearch; - property OnInitChildren: TVTInitChildrenEvent read FOnInitChildren write FOnInitChildren; - property OnInitNode: TVTInitNodeEvent read FOnInitNode write FOnInitNode; - property OnKeyAction: TVTKeyActionEvent read FOnKeyAction write FOnKeyAction; - property OnLoadNode: TVTSaveNodeEvent read FOnLoadNode write FOnLoadNode; - property OnLoadTree: TVTSaveTreeEvent read FOnLoadTree write FOnLoadTree; - property OnMeasureItem: TVTMeasureItemEvent read FOnMeasureItem write FOnMeasureItem; - property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter; - property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave; - property OnNodeClick: TVTNodeClickEvent read FOnNodeClick write FOnNodeClick; - property OnNodeCopied: TVTNodeCopiedEvent read FOnNodeCopied write FOnNodeCopied; - property OnNodeCopying: TVTNodeCopyingEvent read FOnNodeCopying write FOnNodeCopying; - property OnNodeDblClick: TVTNodeClickEvent read FOnNodeDblClick write FOnNodeDblClick; - property OnNodeExport: TVTNodeExportEvent read FOnNodeExport write FOnNodeExport; - property OnNodeHeightTracking: TVTNodeHeightTrackingEvent read FOnNodeHeightTracking write FOnNodeHeightTracking; - property OnNodeHeightDblClickResize: TVTNodeHeightDblClickResizeEvent read FOnNodeHeightDblClickResize - write FOnNodeHeightDblClickResize; - property OnNodeMoved: TVTNodeMovedEvent read FOnNodeMoved write FOnNodeMoved; - property OnNodeMoving: TVTNodeMovingEvent read FOnNodeMoving write FOnNodeMoving; - property OnPaintBackground: TVTBackgroundPaintEvent read FOnPaintBackground write FOnPaintBackground; - property OnPrepareButtonBitmaps : TVTPrepareButtonImagesEvent read FOnPrepareButtonImages write SetOnPrepareButtonImages; - property OnRemoveFromSelection: TVTRemoveFromSelectionEvent read FOnRemoveFromSelection write FOnRemoveFromSelection; - property OnRenderOLEData: TVTRenderOLEDataEvent read FOnRenderOLEData write FOnRenderOLEData; - property OnResetNode: TVTChangeEvent read FOnResetNode write FOnResetNode; - property OnSaveNode: TVTSaveNodeEvent read FOnSaveNode write FOnSaveNode; - property OnSaveTree: TVTSaveTreeEvent read FOnSaveTree write FOnSaveTree; - property OnScroll: TVTScrollEvent read FOnScroll write FOnScroll; - property OnShowScrollBar: TVTScrollBarShowEvent read FOnShowScrollBar write FOnShowScrollBar; - property OnBeforeGetCheckState: TVTBeforeGetCheckStateEvent read FOnBeforeGetCheckState write FOnBeforeGetCheckState; - property OnStartOperation: TVTOperationEvent read FOnStartOperation write FOnStartOperation; - property OnStateChange: TVTStateChangeEvent read FOnStateChange write FOnStateChange; - property OnStructureChange: TVTStructureChangeEvent read FOnStructureChange write FOnStructureChange; - property OnUpdating: TVTUpdatingEvent read FOnUpdating write FOnUpdating; - public - constructor Create(AOwner: TComponent); override; - destructor Destroy; override; - function AbsoluteIndex(Node: PVirtualNode): Cardinal; - function AddChild(Parent: PVirtualNode; UserData: Pointer = nil): PVirtualNode; overload; virtual; - function AddChild(Parent: PVirtualNode; const UserData: IInterface): PVirtualNode; overload; - function AddChild(Parent: PVirtualNode; const UserData: TObject): PVirtualNode; overload; - procedure AddFromStream(Stream: TStream; TargetNode: PVirtualNode); - procedure AfterConstruction; override; - procedure Assign(Source: TPersistent); override; - procedure BeginDrag(Immediate: Boolean; Threshold: Integer = -1); - procedure BeginSynch; - procedure BeginUpdate; virtual; - procedure CancelCutOrCopy; - function CancelEditNode: Boolean; - procedure CancelOperation; - function CanEdit(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; - function CanFocus: Boolean; override; - procedure Clear; virtual; - procedure ClearChecked; - procedure ClearSelection(); overload; inline; - function CopyTo(Source: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode; - ChildrenOnly: Boolean): PVirtualNode; overload; - function CopyTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode; - ChildrenOnly: Boolean): PVirtualNode; overload; - procedure CopyToClipboard; virtual; - procedure CutToClipboard; virtual; - procedure DeleteChildren(Node: PVirtualNode; ResetHasChildren: Boolean = False); - procedure DeleteNode(Node: PVirtualNode; pReIndex: Boolean = True); overload; inline; - procedure DeleteNodes(const pNodes: TNodeArray); - procedure DeleteSelectedNodes; virtual; - function Dragging: Boolean; - function EditNode(Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; - function EndEditNode: Boolean; - procedure EndSynch; - procedure EndUpdate; virtual; - procedure EnsureNodeSelected(); virtual; - function ExecuteAction(Action: TBasicAction): Boolean; override; - procedure FinishCutOrCopy; - procedure FlushClipboard; - procedure FullCollapse(Node: PVirtualNode = nil); virtual; - procedure FullExpand(Node: PVirtualNode = nil); virtual; - function GetControlsAlignment: TAlignment; override; - function GetDisplayRect(Node: PVirtualNode; Column: TColumnIndex; TextOnly: Boolean; Unclipped: Boolean = False; - ApplyCellContentMargin: Boolean = False): TRect; - function GetEffectivelyFiltered(Node: PVirtualNode): Boolean; - function GetEffectivelyVisible(Node: PVirtualNode): Boolean; - function GetFirst(ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetFirstChecked(State: TCheckState = csCheckedNormal; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetFirstChild(Node: PVirtualNode): PVirtualNode; - function GetFirstChildNoInit(Node: PVirtualNode): PVirtualNode; - function GetFirstCutCopy(ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetFirstInitialized(ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetFirstLeaf: PVirtualNode; - function GetFirstLevel(NodeLevel: Cardinal): PVirtualNode; - function GetFirstNoInit(ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetFirstSelected(ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetFirstVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; - IncludeFiltered: Boolean = False): PVirtualNode; - function GetFirstVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - function GetFirstVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - function GetFirstVisibleNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; - IncludeFiltered: Boolean = False): PVirtualNode; - procedure GetHitTestInfoAt(X, Y: Integer; Relative: Boolean; var HitInfo: THitInfo); virtual; - function GetLast(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetLastInitialized(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetLastNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetLastChild(Node: PVirtualNode): PVirtualNode; - function GetLastChildNoInit(Node: PVirtualNode): PVirtualNode; - function GetLastVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; - IncludeFiltered: Boolean = False): PVirtualNode; - function GetLastVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - function GetLastVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - function GetLastVisibleNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; - IncludeFiltered: Boolean = False): PVirtualNode; - function GetMaxColumnWidth(Column: TColumnIndex; UseSmartColumnWidth: Boolean = False): Integer; virtual; - function GetNext(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetNextChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal; - ConsiderChildrenAbove: Boolean = False): PVirtualNode; overload; - function GetNextChecked(Node: PVirtualNode; ConsiderChildrenAbove: Boolean): PVirtualNode; overload; - function GetNextCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetNextInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetNextLeaf(Node: PVirtualNode): PVirtualNode; - function GetNextLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode; - function GetNextNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetNextSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetNextSibling(Node: PVirtualNode): PVirtualNode; - function GetNextSiblingNoInit(Node: PVirtualNode): PVirtualNode; - function GetNextVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; - function GetNextVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; - function GetNextVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - function GetNextVisibleSiblingNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - function GetNodeAt(const P: TPoint): PVirtualNode; overload; inline; - function GetNodeAt(X, Y: Integer): PVirtualNode; overload; - function GetNodeAt(X, Y: Integer; Relative: Boolean; var NodeTop: Integer): PVirtualNode; overload; - function GetNodeData(Node: PVirtualNode): Pointer; overload; - function GetNodeData(pNode: PVirtualNode): T; overload; inline; - function GetSelectedData(): TArray; overload; - function GetInterfaceFromNodeData(pNode: PVirtualNode): T; overload; inline; - function GetNodeDataAt(pXCoord: Integer; pYCoord: Integer): T; - function GetFirstSelectedNodeData(): T; - function GetNodeLevel(Node: PVirtualNode): Cardinal; - function GetNodeLevelForSelectConstraint(Node: PVirtualNode): integer; - function GetOffset(pElement: TVTElement; pNode: PVirtualNode): integer; - procedure GetOffsets(pNode: PVirtualNode; out pOffsets: TVTOffsets; pElement: TVTElement = TVTElement.ofsEndOfClientArea; pColumn: Integer = NoColumn); - function GetPrevious(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetPreviousChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal; - ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetPreviousCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetPreviousInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetPreviousLeaf(Node: PVirtualNode): PVirtualNode; - function GetPreviousLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode; - function GetPreviousNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetPreviousSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - function GetPreviousSibling(Node: PVirtualNode): PVirtualNode; - function GetPreviousSiblingNoInit(Node: PVirtualNode): PVirtualNode; - function GetPreviousVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; - function GetPreviousVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; - function GetPreviousVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - function GetPreviousVisibleSiblingNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - function GetSortedCutCopySet(Resolve: Boolean): TNodeArray; - function GetSortedSelection(Resolve: Boolean): TNodeArray; - procedure GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect; - var Text: string); virtual; - function GetTreeRect: TRect; - function GetVisibleParent(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - function HasAsParent(Node, PotentialParent: PVirtualNode): Boolean; - function InsertNode(Node: PVirtualNode; Mode: TVTNodeAttachMode; UserData: Pointer = nil): PVirtualNode; - procedure InvalidateChildren(Node: PVirtualNode; Recursive: Boolean); - procedure InvalidateColumn(Column: TColumnIndex); - function InvalidateNode(Node: PVirtualNode): TRect; virtual; - procedure InvalidateToBottom(Node: PVirtualNode); - procedure InvertSelection(VisibleOnly: Boolean); - function IsEditing: Boolean; - function IsMouseSelecting: Boolean; - function IsEmpty: Boolean; inline; - function IsUpdating(): Boolean; - function IterateSubtree(Node: PVirtualNode; Callback: TVTGetNodeProc; Data: Pointer; Filter: TVirtualNodeStates = []; - DoInit: Boolean = False; ChildNodesOnly: Boolean = False): PVirtualNode; - procedure LoadFromFile(const FileName: TFileName); virtual; - procedure LoadFromStream(Stream: TStream); virtual; - procedure MeasureItemHeight(const Canvas: TCanvas; Node: PVirtualNode); virtual; - procedure MoveTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode; ChildrenOnly: Boolean); overload; - procedure MoveTo(Node: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode; - ChildrenOnly: Boolean); overload; - procedure PaintTree(TargetCanvas: TCanvas; Window: TRect; Target: TPoint; PaintOptions: TVTInternalPaintOptions; - PixelFormat: TPixelFormat = pfDevice); virtual; - function PasteFromClipboard: Boolean; virtual; - procedure PrepareDragImage(HotSpot: TPoint; const DataObject: IDataObject); - procedure Print(Printer: TPrinter; PrintHeader: Boolean); - function ProcessDrop(const DataObject: IDataObject; TargetNode: PVirtualNode; var Effect: Integer; Mode: - TVTNodeAttachMode): Boolean; - function ProcessOLEData(Source: TBaseVirtualTree; const DataObject: IDataObject; TargetNode: PVirtualNode; - Mode: TVTNodeAttachMode; Optimized: Boolean): Boolean; - procedure RepaintNode(Node: PVirtualNode); - procedure ReinitChildren(Node: PVirtualNode; Recursive: Boolean); virtual; - procedure InitRecursive(Node: PVirtualNode; Levels: Cardinal = MaxInt; pVisibleOnly: Boolean = True); - procedure ReinitNode(Node: PVirtualNode; Recursive: Boolean); virtual; - procedure ResetNode(Node: PVirtualNode); virtual; - procedure SaveToFile(const FileName: TFileName); - procedure SaveToStream(Stream: TStream; Node: PVirtualNode = nil); virtual; - function ScaledPixels(pPixels: Integer): Integer; - procedure ScaleNodeHeights(M, D: Integer); - function ScrollIntoView(Node: PVirtualNode; Center: Boolean; Horizontally: Boolean = False): Boolean; overload; - function ScrollIntoView(Column: TColumnIndex; Center: Boolean; Node: PVirtualNode = nil): Boolean; overload; - procedure SelectAll(VisibleOnly: Boolean); - procedure SetCheckStateForAll(aCheckState: TCheckState; pSelectedOnly: Boolean; pExcludeDisabled: Boolean = True); - procedure SetNodeData(pNode: PVirtualNode; pUserData: Pointer); overload; inline; - procedure SetNodeData(pNode: PVirtualNode; const pUserData: IInterface); overload; inline; - procedure SetNodeData(pNode: PVirtualNode; pUserData: T); overload; - procedure Sort(Node: PVirtualNode; Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); virtual; - procedure SortTree(Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); virtual; - procedure ToggleNode(Node: PVirtualNode); - procedure UpdateHorizontalRange; virtual; - procedure UpdateHorizontalScrollBar(DoRepaint: Boolean); - procedure UpdateRanges; - procedure UpdateScrollBars(DoRepaint: Boolean); virtual; - procedure UpdateVerticalRange; - procedure UpdateVerticalScrollBar(DoRepaint: Boolean); - function UseRightToLeftReading: Boolean; - procedure ValidateChildren(Node: PVirtualNode; Recursive: Boolean); - procedure ValidateNode(Node: PVirtualNode; Recursive: Boolean); - - { Enumerations } - function Nodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; - function CheckedNodes(State: TCheckState = csCheckedNormal; ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; - function ChildNodes(Node: PVirtualNode): TVTVirtualNodeEnumeration; - function CutCopyNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; - function InitializedNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; - function LeafNodes: TVTVirtualNodeEnumeration; - function LevelNodes(NodeLevel: Cardinal): TVTVirtualNodeEnumeration; - function NoInitNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; - function SelectedNodes(ConsiderChildrenAbove: Boolean = False): TVTVirtualNodeEnumeration; - function VisibleNodes(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; - IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration; - function VisibleChildNodes(Node: PVirtualNode; IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration; - function VisibleChildNoInitNodes(Node: PVirtualNode; IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration; - function VisibleNoInitNodes(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; - IncludeFiltered: Boolean = False): TVTVirtualNodeEnumeration; - property Accessible: IAccessible read FAccessible write FAccessible; - property AccessibleItem: IAccessible read FAccessibleItem write FAccessibleItem; - property AccessibleName: string read FAccessibleName write FAccessibleName; - property BottomNode: PVirtualNode read GetBottomNode write SetBottomNode; - property CheckedCount: Integer read GetCheckedCount; - property CheckImages: TCustomImageList read FCheckImages; - property CheckState[Node: PVirtualNode]: TCheckState read GetCheckState write SetCheckState; - property CheckType[Node: PVirtualNode]: TCheckType read GetCheckType write SetCheckType; - property ChildCount[Node: PVirtualNode]: Cardinal read GetChildCount write SetChildCount; - property ChildrenInitialized[Node: PVirtualNode]: Boolean read GetChildrenInitialized; - property CutCopyCount: Integer read GetCutCopyCount; - property DragImage: TVTDragImage read FDragImage; - property DragManager: IVTDragManager read GetDragManager; - property DropTargetNode: PVirtualNode read FDropTargetNode write FDropTargetNode; - property EditLink: IVTEditLink read FEditLink; - property EmptyListMessage: string read FEmptyListMessage write SetEmptyListMessage; - property Expanded[Node: PVirtualNode]: Boolean read GetExpanded write SetExpanded; - property FocusedColumn: TColumnIndex read FFocusedColumn write SetFocusedColumn default InvalidColumn; - property FocusedNode: PVirtualNode read FFocusedNode write SetFocusedNode; - property Font; - property FullyVisible[Node: PVirtualNode]: Boolean read GetFullyVisible write SetFullyVisible; - property HasChildren[Node: PVirtualNode]: Boolean read GetHasChildren write SetHasChildren; - property Header: TVTHeader read FHeader write SetHeader; - property HotNode: PVirtualNode read FCurrentHotNode write SetHotNode; - property HotColumn: TColumnIndex read FCurrentHotColumn; - property IsDisabled[Node: PVirtualNode]: Boolean read GetDisabled write SetDisabled; - property IsEffectivelyFiltered[Node: PVirtualNode]: Boolean read GetEffectivelyFiltered; - property IsEffectivelyVisible[Node: PVirtualNode]: Boolean read GetEffectivelyVisible; - property IsFiltered[Node: PVirtualNode]: Boolean read GetFiltered write SetFiltered; - property IsVisible[Node: PVirtualNode]: Boolean read GetVisible write SetVisible; - property MultiLine[Node: PVirtualNode]: Boolean read GetMultiline write SetMultiline; - property NodeHeight[Node: PVirtualNode]: Cardinal read GetNodeHeight write SetNodeHeight; - property NodeParent[Node: PVirtualNode]: PVirtualNode read GetNodeParent write SetNodeParent; - property OffsetX: Integer read FOffsetX write SetOffsetX; - property OffsetXY: TPoint read GetOffsetXY write SetOffsetXY; - property OffsetY: Integer read FOffsetY write SetOffsetY; - property OperationCount: Cardinal read FOperationCount; - property RootNode: PVirtualNode read FRoot; - property SearchBuffer: string read FSearchBuffer; - property Selected[Node: PVirtualNode]: Boolean read GetSelected write SetSelected; - property SelectionLocked: Boolean read FSelectionLocked write FSelectionLocked; - property TotalCount: Cardinal read GetTotalCount; - property TreeStates: TVirtualTreeStates read FStates write FStates; - property SelectedCount: Integer read FSelectionCount; - property TopNode: PVirtualNode read GetTopNode write SetTopNode; - property VerticalAlignment[Node: PVirtualNode]: Byte read GetVerticalAlignment write SetVerticalAlignment; - property VisibleCount: Cardinal read FVisibleCount; - property VisiblePath[Node: PVirtualNode]: Boolean read GetVisiblePath write SetVisiblePath; - property UpdateCount: Cardinal read FUpdateCount; - property DoubleBuffered: Boolean read GetDoubleBuffered write SetDoubleBuffered default True; - end; - - - // --------- TCustomVirtualStringTree - - // Options regarding strings (useful only for the string tree and descendants): - TVTStringOption = ( - toSaveCaptions, // If set then the caption is automatically saved with the tree node, regardless of what is - // saved in the user data. - toShowStaticText, // Show static text in a caption which can be differently formatted than the caption - // but cannot be edited. - toAutoAcceptEditChange // Automatically accept changes during edit if the user finishes editing other then - // VK_RETURN or ESC. If not set then changes are cancelled. - ); - TVTStringOptions = set of TVTStringOption; - -const - DefaultStringOptions = [toSaveCaptions, toAutoAcceptEditChange]; - -type - TCustomStringTreeOptions = class(TCustomVirtualTreeOptions) - private - FStringOptions: TVTStringOptions; - procedure SetStringOptions(const Value: TVTStringOptions); - protected - property StringOptions: TVTStringOptions read FStringOptions write SetStringOptions default DefaultStringOptions; - public - constructor Create(AOwner: TBaseVirtualTree); override; - - procedure AssignTo(Dest: TPersistent); override; - end; - - TStringTreeOptions = class(TCustomStringTreeOptions) - published - property AnimationOptions; - property AutoOptions; - property ExportMode; - property MiscOptions; - property PaintOptions; - property SelectionOptions; - property StringOptions; - property EditOptions; - end; - - TCustomVirtualStringTree = class; - - // Edit support Classes. - TStringEditLink = class; - - TVTEdit = class(TCustomEdit) - private - procedure CMAutoAdjust(var Message: TMessage); message CM_AUTOADJUST; - procedure CMExit(var Message: TMessage); message CM_EXIT; - procedure CMRelease(var Message: TMessage); message CM_RELEASE; - procedure CNCommand(var Message: TWMCommand); message CN_COMMAND; - procedure WMChar(var Message: TWMChar); message WM_CHAR; - procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY; - procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE; - procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN; - protected - FRefLink: IVTEditLink; - FLink: TStringEditLink; - procedure AutoAdjustSize; virtual; - function CalcMinHeight: Integer; virtual; - procedure CreateParams(var Params: TCreateParams); override; - function GetTextSize: TSize; virtual; - procedure KeyPress(var Key: Char); override; - public - constructor Create(Link: TStringEditLink); reintroduce; - procedure ClearLink; - procedure ClearRefLink; - procedure Release; virtual; - - property AutoSelect; - property AutoSize; + property AccessibleName; + property Action; + property Align; + property Alignment; + property Anchors; + property AnimationDuration; + property AutoExpandDelay; + property AutoScrollDelay; + property AutoScrollInterval; + property Background; + property BackGroundImageTransparent; + property BackgroundOffsetX; + property BackgroundOffsetY; + property BiDiMode; + property BevelEdges; + property BevelInner; + property BevelOuter; + property BevelKind; + property BevelWidth; property BorderStyle; - property CharCase; - property HideSelection; - property MaxLength; - property OEMConvert; - property PasswordChar; - end; - - TStringEditLink = class(TInterfacedObject, IVTEditLink) - private - FEdit: TVTEdit; // A normal custom edit control. - protected - FTree: TCustomVirtualStringTree; // A back reference to the tree calling. - FNode: PVirtualNode; // The node to be edited. - FColumn: TColumnIndex; // The column of the node. - FAlignment: TAlignment; - FTextBounds: TRect; // Smallest rectangle around the text. - FStopping: Boolean; // Set to True when the edit link requests stopping the edit action. - procedure SetEdit(const Value: TVTEdit); // Setter for the FEdit member; - public - constructor Create; virtual; - destructor Destroy; override; - property Alignment : TAlignment read FAlignment; - property Node : PVirtualNode read FNode; // [IPK] Make FNode accessible - property Column: TColumnIndex read FColumn; // [IPK] Make Column(Index) accessible - - function BeginEdit: Boolean; virtual; stdcall; - function CancelEdit: Boolean; virtual; stdcall; - property Edit: TVTEdit read FEdit write SetEdit; - function EndEdit: Boolean; virtual; stdcall; - function GetBounds: TRect; virtual; stdcall; - function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; virtual; stdcall; - procedure ProcessMessage(var Message: TMessage); virtual; stdcall; - procedure SetBounds(R: TRect); virtual; stdcall; - property Stopping : boolean read FStopping; - property Tree : TCustomVirtualStringTree read FTree; - end; - - // Describes the type of text to return in the text and draw info retrival events. - TVSTTextType = ( - ttNormal, // normal label of the node, this is also the text which can be edited - ttStatic // static (non-editable) text after the normal text - ); - - // Describes the source to use when converting a string tree into a string for clipboard etc. - TVSTTextSourceType = ( - tstAll, // All nodes are rendered. Initialization is done on the fly. - tstInitialized, // Only initialized nodes are rendered. - tstSelected, // Only selected nodes are rendered. - tstCutCopySet, // Only nodes currently marked as being in the cut/copy clipboard set are rendered. - tstVisible, // Only visible nodes are rendered. - tstChecked // Only checked nodes are rendered - ); - - TVTPaintText = procedure(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; - TextType: TVSTTextType) of object; - TVSTGetTextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; - TextType: TVSTTextType; var CellText: string) of object; - TVSTGetHintEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; - var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: string) of object; - // New text can only be set for variable caption. - TVSTNewTextEvent = procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; - NewText: string) of object; - TVSTShortenStringEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; - Column: TColumnIndex; const S: string; TextSpace: Integer; var Result: string; - var Done: Boolean) of object; - TVTMeasureTextEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; - Column: TColumnIndex; const Text: string; var Extent: Integer) of object; - TVTDrawTextEvent = procedure(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; - Column: TColumnIndex; const Text: string; const CellRect: TRect; var DefaultDraw: Boolean) of object; - - /// Event arguments of the OnGetCellText event - TVSTGetCellTextEventArgs = record - Node: PVirtualNode; - Column: TColumnIndex; - CellText: string; - StaticText: string; - StaticTextAlignment: TAlignment; - ExportType: TVTExportType; - constructor Create(pNode: PVirtualNode; pColumn: TColumnIndex; pExportType: TVTExportType = TVTExportType.etNone); - end; - - /// Event signature which is called when text is painted on the canvas or needed for the export. - TVSTGetCellTextEvent = procedure(Sender: TCustomVirtualStringTree; var E: TVSTGetCellTextEventArgs) of object; - - TCustomVirtualStringTree = class(TBaseVirtualTree) - private - FInternalDataOffset: Cardinal; // offset to the internal data of the string tree - FDefaultText: string; // text to show if there's no OnGetText event handler (e.g. at design time) - FTextHeight: Integer; // true size of the font - FEllipsisWidth: Integer; // width of '...' for the current font - - FOnPaintText: TVTPaintText; // triggered before either normal or fixed text is painted to allow - // even finer customization (kind of sub cell painting) - FOnGetText: TVSTGetTextEvent; // used to retrieve the string to be displayed for a specific node - fOnGetCellText: TVSTGetCellTextEvent; // used to retrieve the normal and static text of a tree node - FOnGetHint: TVSTGetHintEvent; // used to retrieve the hint to be displayed for a specific node - FOnNewText: TVSTNewTextEvent; // used to notify the application about an edited node caption - FOnShortenString: TVSTShortenStringEvent; // used to allow the application a customized string shortage - FOnMeasureTextWidth: TVTMeasureTextEvent; // used to adjust the width of the cells - FOnMeasureTextHeight: TVTMeasureTextEvent; - FOnDrawText: TVTDrawTextEvent; // used to custom draw the node text - /// Returns True if the property DefaultText has a value that differs from the default value, False otherwise. - function IsDefaultTextStored(): Boolean; - function GetImageText(Node: PVirtualNode; Kind: TVTImageKind; - Column: TColumnIndex): string; - function GetOptions: TCustomStringTreeOptions; - function GetStaticText(Node: PVirtualNode; Column: TColumnIndex): string; - function GetText(Node: PVirtualNode; Column: TColumnIndex): string; - procedure ReadText(Reader: TReader); - procedure WriteText(Writer: TWriter); - procedure ResetInternalData(Node: PVirtualNode; Recursive: Boolean); - procedure SetDefaultText(const Value: string); - procedure SetOptions(const Value: TCustomStringTreeOptions); - procedure SetText(Node: PVirtualNode; Column: TColumnIndex; const Value: string); - procedure WMSetFont(var Msg: TWMSetFont); message WM_SETFONT; - procedure GetDataFromGrid(const AStrings : TStringList; const IncludeHeading : Boolean = True); - protected - FPreviouslySelected: TStringList; - procedure InitializeTextProperties(var PaintInfo: TVTPaintInfo); // [IPK] - private to protected - procedure PaintNormalText(var PaintInfo: TVTPaintInfo; TextOutFlags: Integer; Text: string); virtual; // [IPK] - private to protected - procedure PaintStaticText(const PaintInfo: TVTPaintInfo; pStaticTextAlignment: TAlignment; const Text: string); virtual; // [IPK] - private to protected - procedure AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; var NextNonEmpty: TColumnIndex); override; - function CanExportNode(Node: PVirtualNode): Boolean; - function CalculateStaticTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: string): Integer; virtual; - function CalculateTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: string): Integer; virtual; - function ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean; override; - procedure DefineProperties(Filer: TFiler); override; - function DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; override; - function DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): string; override; - function DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle): string; override; - function DoGetNodeExtraWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; override; - function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; override; - procedure DoGetText(var pEventArgs: TVSTGetCellTextEventArgs); virtual; - function DoIncrementalSearch(Node: PVirtualNode; const Text: string): Integer; override; - procedure DoNewText(Node: PVirtualNode; Column: TColumnIndex; const Text: string); virtual; - procedure DoPaintNode(var PaintInfo: TVTPaintInfo); override; - procedure DoPaintText(Node: PVirtualNode; const Canvas: TCanvas; Column: TColumnIndex; - TextType: TVSTTextType); virtual; - function DoShortenString(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const S: string; Width: Integer; - EllipsisWidth: Integer = 0): string; virtual; - procedure DoTextDrawing(var PaintInfo: TVTPaintInfo; const Text: string; CellRect: TRect; DrawFormat: Cardinal); virtual; - function DoTextMeasuring(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: string): TSize; virtual; - function GetOptionsClass: TTreeOptionsClass; override; - procedure GetRenderStartValues(Source: TVSTTextSourceType; var Node: PVirtualNode; - var NextNodeProc: TGetNextNodeProc); - function InternalData(Node: PVirtualNode): Pointer; - procedure MainColumnChanged; override; - function ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType, - ChunkSize: Integer): Boolean; override; - procedure ReadOldStringOptions(Reader: TReader); - function RenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; ForClipboard: Boolean): HResult; override; - procedure SetChildCount(Node: PVirtualNode; NewChildCount: Cardinal); override; - procedure WriteChunks(Stream: TStream; Node: PVirtualNode); override; - - property DefaultText: string read FDefaultText write SetDefaultText stored False;// Stored via own writer - property EllipsisWidth: Integer read FEllipsisWidth; - property TreeOptions: TCustomStringTreeOptions read GetOptions write SetOptions; - - property OnGetHint: TVSTGetHintEvent read FOnGetHint write FOnGetHint; - property OnGetText: TVSTGetTextEvent read FOnGetText write FOnGetText; - property OnGetCellText: TVSTGetCellTextEvent read fOnGetCellText write fOnGetCellText; - property OnNewText: TVSTNewTextEvent read FOnNewText write FOnNewText; - property OnPaintText: TVTPaintText read FOnPaintText write FOnPaintText; - property OnShortenString: TVSTShortenStringEvent read FOnShortenString write FOnShortenString; - property OnMeasureTextWidth: TVTMeasureTextEvent read FOnMeasureTextWidth write FOnMeasureTextWidth; - property OnMeasureTextHeight: TVTMeasureTextEvent read FOnMeasureTextHeight write FOnMeasureTextHeight; - property OnDrawText: TVTDrawTextEvent read FOnDrawText write FOnDrawText; - public - constructor Create(AOwner: TComponent); override; - destructor Destroy(); override; - function AddChild(Parent: PVirtualNode; UserData: Pointer = nil): PVirtualNode; override; - function ComputeNodeHeight(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; S: string = ''): Integer; virtual; - function ContentToClipboard(Format: Word; Source: TVSTTextSourceType): HGLOBAL; - procedure ContentToCustom(Source: TVSTTextSourceType); - function ContentToHTML(Source: TVSTTextSourceType; const Caption: string = ''): String; - function ContentToRTF(Source: TVSTTextSourceType): RawByteString; - function ContentToText(Source: TVSTTextSourceType; Separator: Char): String; overload; - function ContentToUnicode(Source: TVSTTextSourceType; Separator: WideChar): string; overload; deprecated 'Use ContentToText instead'; - function ContentToText(Source: TVSTTextSourceType; const Separator: string): string; overload; - procedure GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect; - var Text: string); override; - function InvalidateNode(Node: PVirtualNode): TRect; override; - function Path(Node: PVirtualNode; Column: TColumnIndex; Delimiter: Char): string; - procedure ReinitNode(Node: PVirtualNode; Recursive: Boolean); override; - procedure AddToSelection(Node: PVirtualNode; NotifySynced: Boolean); override; - procedure AddToSelection(const NewItems: TNodeArray; NewLength: Integer; ForceInsert: Boolean); override; - procedure RemoveFromSelection(Node: PVirtualNode); override; - function SaveToCSVFile(const FileNameWithPath : TFileName; const IncludeHeading : Boolean) : Boolean; - /// Alternate text for images used in Accessibility. - property ImageText[Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex]: string read GetImageText; - property StaticText[Node: PVirtualNode; Column: TColumnIndex]: string read GetStaticText; - property Text[Node: PVirtualNode; Column: TColumnIndex]: string read GetText write SetText; - end; - - [ComponentPlatformsAttribute(pidWin32 or pidWin64)] - TVirtualStringTree = class(TCustomVirtualStringTree) - private - - function GetOptions: TStringTreeOptions; - procedure SetOptions(const Value: TStringTreeOptions); - protected - function GetOptionsClass: TTreeOptionsClass; override; - public - - property Canvas; - property RangeX; - property LastDragEffect; - property CheckImageKind; // should no more be published to make #622 fix working - published - property AccessibleName; - property Action; - property Align; - property Alignment; - property Anchors; - property AnimationDuration; - property AutoExpandDelay; - property AutoScrollDelay; - property AutoScrollInterval; - property Background; - property BackGroundImageTransparent; - property BackgroundOffsetX; - property BackgroundOffsetY; - property BiDiMode; - property BevelEdges; - property BevelInner; - property BevelOuter; - property BevelKind; - property BevelWidth; - property BorderStyle; - property BottomSpace; - property ButtonFillMode; - property ButtonStyle; - property BorderWidth; - property ChangeDelay; - property ClipboardFormats; - property Color; - property Colors; - property Constraints; - property Ctl3D; - property CustomCheckImages; - property DefaultNodeHeight; - property DefaultPasteMode; - property DefaultText; - property DragCursor; - property DragHeight; - property DragKind; - property DragImageKind; - property DragMode; - property DragOperations; - property DragType; - property DragWidth; - property DrawSelectionMode; - property EditDelay; - property EmptyListMessage; - property Enabled; - property Font; - property Header; - property HintMode; - property HotCursor; - property Images; - property IncrementalSearch; - property IncrementalSearchDirection; - property IncrementalSearchStart; - property IncrementalSearchTimeout; - property Indent; - property LineMode; - property LineStyle; - property Margin; - property NodeAlignment; - property NodeDataSize; - property OperationCanceled; - property ParentBiDiMode; - property ParentColor default False; - property ParentCtl3D; - property ParentFont; - property ParentShowHint; - property PopupMenu; - property RootNodeCount; - property ScrollBarOptions; - property SelectionBlendFactor; - property SelectionCurveRadius; - property ShowHint; - property StateImages; - property StyleElements; - {$if CompilerVersion >= 34}property StyleName;{$ifend} - property TabOrder; - property TabStop default True; - property TextMargin; - property TreeOptions: TStringTreeOptions read GetOptions write SetOptions; - property Visible; - property WantTabs; + property BottomSpace; + property ButtonFillMode; + property ButtonStyle; + property BorderWidth; + property ChangeDelay; + property ClipboardFormats; + property Color; + property Colors; + property Constraints; + property Ctl3D; + property CustomCheckImages; + property DefaultNodeHeight; + property DefaultPasteMode; + property DefaultText; + property DragCursor; + property DragHeight; + property DragKind; + property DragImageKind; + property DragMode; + property DragOperations; + property DragType; + property DragWidth; + property DrawSelectionMode; + property EditDelay; + property EmptyListMessage; + property Enabled; + property Font; + property Header; + property HintMode; + property HotCursor; + property Images; + property IncrementalSearch; + property IncrementalSearchDirection; + property IncrementalSearchStart; + property IncrementalSearchTimeout; + property Indent; + property LineMode; + property LineStyle; + property Margin; + property NodeAlignment; + property NodeDataSize; + property OperationCanceled; + property ParentBiDiMode; + property ParentColor default False; + property ParentCtl3D; + property ParentFont; + property ParentShowHint; + property PopupMenu; + property RootNodeCount; + property ScrollBarOptions; + property SelectionBlendFactor; + property SelectionCurveRadius; + property ShowHint; + property StateImages; + property StyleElements; + {$if CompilerVersion >= 34}property StyleName;{$ifend} + property TabOrder; + property TabStop default True; + property TextMargin; + property TreeOptions: TStringTreeOptions read GetOptions write SetOptions; + property Visible; + property WantTabs; property OnAddToSelection; property OnAdvancedHeaderDraw; @@ -3667,6 +470,8 @@ TVirtualStringTree = class(TCustomVirtualStringTree) property OnClick; property OnCollapsed; property OnCollapsing; + property OnColumnChecked; + property OnColumnChecking; property OnColumnClick; property OnColumnDblClick; property OnColumnExport; @@ -3777,29854 +582,56 @@ TVirtualStringTree = class(TCustomVirtualStringTree) property OnUpdating; property OnCanResize; property OnGesture; - property Touch; - end; - - TVTDrawNodeEvent = procedure(Sender: TBaseVirtualTree; const PaintInfo: TVTPaintInfo) of object; - TVTGetCellContentMarginEvent = procedure(Sender: TBaseVirtualTree; HintCanvas: TCanvas; Node: PVirtualNode; - Column: TColumnIndex; CellContentMarginType: TVTCellContentMarginType; var CellContentMargin: TPoint) of object; - TVTGetNodeWidthEvent = procedure(Sender: TBaseVirtualTree; HintCanvas: TCanvas; Node: PVirtualNode; - Column: TColumnIndex; var NodeWidth: Integer) of object; - - // Tree descendant to let an application draw its stuff itself. - TCustomVirtualDrawTree = class(TBaseVirtualTree) - private - FOnDrawNode: TVTDrawNodeEvent; - FOnGetCellContentMargin: TVTGetCellContentMarginEvent; - FOnGetNodeWidth: TVTGetNodeWidthEvent; - protected - function DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex; - CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; override; - function DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; override; - procedure DoPaintNode(var PaintInfo: TVTPaintInfo); override; - function GetDefaultHintKind: TVTHintKind; override; - - property OnDrawNode: TVTDrawNodeEvent read FOnDrawNode write FOnDrawNode; - property OnGetCellContentMargin: TVTGetCellContentMarginEvent read FOnGetCellContentMargin write FOnGetCellContentMargin; - property OnGetNodeWidth: TVTGetNodeWidthEvent read FOnGetNodeWidth write FOnGetNodeWidth; - end; - - [ComponentPlatformsAttribute(pidWin32 or pidWin64)] - TVirtualDrawTree = class(TCustomVirtualDrawTree) - private - function GetOptions: TVirtualTreeOptions; - procedure SetOptions(const Value: TVirtualTreeOptions); - protected - function GetOptionsClass: TTreeOptionsClass; override; - public - property Canvas; - property LastDragEffect; - property CheckImageKind; // should no more be published to make #622 fix working - published - property Action; - property Align; - property Alignment; - property Anchors; - property AnimationDuration; - property AutoExpandDelay; - property AutoScrollDelay; - property AutoScrollInterval; - property Background; - property BackgroundOffsetX; - property BackgroundOffsetY; - property BiDiMode; - property BevelEdges; - property BevelInner; - property BevelOuter; - property BevelKind; - property BevelWidth; - property BorderStyle; - property BottomSpace; - property ButtonFillMode; - property ButtonStyle; - property BorderWidth; - property ChangeDelay; - property ClipboardFormats; - property Color; - property Colors; - property Constraints; - property Ctl3D; - property CustomCheckImages; - property DefaultNodeHeight; - property DefaultPasteMode; - property DragCursor; - property DragHeight; - property DragKind; - property DragImageKind; - property DragMode; - property DragOperations; - property DragType; - property DragWidth; - property DrawSelectionMode; - property EditDelay; - property Enabled; - property Font; - property Header; - property HintMode; - property HotCursor; - property Images; - property IncrementalSearch; - property IncrementalSearchDirection; - property IncrementalSearchStart; - property IncrementalSearchTimeout; - property Indent; - property LineMode; - property LineStyle; - property Margin; - property NodeAlignment; - property NodeDataSize; - property OperationCanceled; - property ParentBiDiMode; - property ParentColor default False; - property ParentCtl3D; - property ParentFont; - property ParentShowHint; - property PopupMenu; - property RootNodeCount; - property ScrollBarOptions; - property SelectionBlendFactor; - property SelectionCurveRadius; - property ShowHint; - property StateImages; - property TabOrder; - property TabStop default True; - property TextMargin; - property TreeOptions: TVirtualTreeOptions read GetOptions write SetOptions; - property Visible; - property WantTabs; - - property OnAddToSelection; - property OnAdvancedHeaderDraw; - property OnAfterAutoFitColumn; - property OnAfterAutoFitColumns; - property OnAfterCellPaint; - property OnAfterColumnExport; - property OnAfterColumnWidthTracking; - property OnAfterGetMaxColumnWidth; - property OnAfterHeaderExport; - property OnAfterHeaderHeightTracking; - property OnAfterItemErase; - property OnAfterItemPaint; - property OnAfterNodeExport; - property OnAfterPaint; - property OnAfterTreeExport; - property OnBeforeAutoFitColumn; - property OnBeforeAutoFitColumns; - property OnBeforeCellPaint; - property OnBeforeColumnExport; - property OnBeforeColumnWidthTracking; - property OnBeforeDrawTreeLine; - property OnBeforeGetMaxColumnWidth; - property OnBeforeHeaderExport; - property OnBeforeHeaderHeightTracking; - property OnBeforeItemErase; - property OnBeforeItemPaint; - property OnBeforeNodeExport; - property OnBeforePaint; - property OnBeforeTreeExport; - property OnCanSplitterResizeColumn; - property OnCanSplitterResizeHeader; - property OnCanSplitterResizeNode; - property OnChange; - property OnChecked; - property OnChecking; - property OnClick; - property OnCollapsed; - property OnCollapsing; - property OnColumnClick; - property OnColumnDblClick; - property OnColumnExport; - property OnColumnResize; - property OnColumnVisibilityChanged; - property OnColumnWidthDblClickResize; - property OnColumnWidthTracking; - property OnCompareNodes; - property OnContextPopup; - property OnCreateDataObject; - property OnCreateDragManager; - property OnCreateEditor; - property OnDblClick; - property OnDragAllowed; - property OnDragOver; - property OnDragDrop; - property OnDrawHint; - property OnDrawNode; - property OnEdited; - property OnEditing; - property OnEndDock; - property OnEndDrag; - property OnEndOperation; - property OnEnter; - property OnExit; - property OnExpanded; - property OnExpanding; - property OnFocusChanged; - property OnFocusChanging; - property OnFreeNode; - property OnGetCellIsEmpty; - property OnGetCursor; - property OnGetHeaderCursor; - property OnGetHelpContext; - property OnGetHintKind; - property OnGetHintSize; - property OnGetImageIndex; - property OnGetImageIndexEx; - property OnGetLineStyle; - property OnGetNodeDataSize; - property OnGetNodeWidth; - property OnGetPopupMenu; - property OnGetUserClipboardFormats; - property OnHeaderAddPopupItem; - property OnHeaderClick; - property OnHeaderDblClick; - property OnHeaderDragged; - property OnHeaderDraggedOut; - property OnHeaderDragging; - property OnHeaderDraw; - property OnHeaderDrawQueryElements; - property OnHeaderHeightTracking; - property OnHeaderHeightDblClickResize; - property OnHeaderMouseDown; - property OnHeaderMouseMove; - property OnHeaderMouseUp; - property OnHotChange; - property OnIncrementalSearch; - property OnInitChildren; - property OnInitNode; - property OnKeyAction; - property OnKeyDown; - property OnKeyPress; - property OnKeyUp; - property OnLoadNode; - property OnLoadTree; - property OnMeasureItem; - property OnMouseDown; - property OnMouseMove; - property OnMouseUp; - property OnMouseWheel; - property OnNodeClick; - property OnNodeCopied; - property OnNodeCopying; - property OnNodeDblClick; - property OnNodeExport; - property OnNodeHeightTracking; - property OnNodeHeightDblClickResize; - property OnNodeMoved; - property OnNodeMoving; - property OnPaintBackground; - property OnPrepareButtonBitmaps; - property OnRemoveFromSelection; - property OnRenderOLEData; - property OnResetNode; - property OnResize; - property OnSaveNode; - property OnSaveTree; - property OnScroll; - property OnShowScrollBar; - property OnStartDock; - property OnStartDrag; - property OnStartOperation; - property OnStateChange; - property OnStructureChange; - property OnUpdating; - property OnCanResize; - property OnGesture; - property Touch; - property StyleElements; - end; - - - -// utility routines -function TreeFromNode(Node: PVirtualNode): TBaseVirtualTree; - - -//---------------------------------------------------------------------------------------------------------------------- - -implementation - -{$R VirtualTrees.res} - -uses - Vcl.Consts, - System.Math, - Vcl.AxCtrls, // TOLEStream - Winapi.MMSystem, // for animation timer (does not include further resources) - System.TypInfo, // for migration stuff - System.SyncObjs, - Vcl.ActnList, - Vcl.StdActns, // for standard action support - System.StrUtils, - Vcl.GraphUtil, // accessibility helper class - VirtualTrees.AccessibilityFactory, - VirtualTrees.StyleHooks, - VirtualTrees.Classes, - VirtualTrees.WorkerThread, - VirtualTrees.ClipBoard, - VirtualTrees.Utils, - VirtualTrees.Export, - VirtualTrees.HeaderPopup; - -resourcestring - // Localizable strings. - SEditLinkIsNil = 'Edit link must not be nil.'; - SWrongMoveError = 'Target node cannot be a child node of the node to be moved.'; - SWrongStreamFormat = 'Unable to load tree structure, the format is wrong.'; - SWrongStreamVersion = 'Unable to load tree structure, the version is unknown.'; - SStreamTooSmall = 'Unable to load tree structure, not enough data available.'; - SCorruptStream1 = 'Stream data corrupt. A node''s anchor chunk is missing.'; - SCorruptStream2 = 'Stream data corrupt. Unexpected data after node''s end position.'; - SClipboardFailed = 'Clipboard operation failed.'; - -const - ClipboardStates = [tsCopyPending, tsCutPending]; - DefaultScrollUpdateFlags = [suoRepaintHeader, suoRepaintScrollBars, suoScrollClientArea, suoUpdateNCArea]; - TreeNodeSize = (SizeOf(TVirtualNode) + (SizeOf(Pointer) - 1)) and not (SizeOf(Pointer) - 1); // used for node allocation and access to internal data - /// Default value of the DefaultText property - cDefaultText = 'Node'; - MouseButtonDown = [tsLeftButtonDown, tsMiddleButtonDown, tsRightButtonDown]; - - // Do not modify the copyright in any way! Usage of this unit is prohibited without the copyright notice - // in the compiled binary file. - Copyright: string = 'Virtual Treeview © 1999-2021 Mike Lischke, Joachim Marder'; - -var - StandardOLEFormat: TFormatEtc = ( - // Format must later be set. - cfFormat: 0; - // No specific target device to render on. - ptd: nil; - // Normal content to render. - dwAspect: DVASPECT_CONTENT; - // No specific page of multipage data (we don't use multipage data by default). - lindex: -1; - // Acceptable storage formats are IStream and global memory. The first is preferred. - tymed: TYMED_ISTREAM or TYMED_HGLOBAL; - ); - -type - // protection against TRect record method that cause problems with with-statements - TWithSafeRect = record - case Integer of - 0: (Left, Top, Right, Bottom: Integer); - 1: (TopLeft, BottomRight: TPoint); - end; - -type // streaming support - TMagicID = array[0..5] of WideChar; - - TChunkHeader = record - ChunkType, - ChunkSize: Integer; // contains the size of the chunk excluding the header - end; - - // base information about a node - TBaseChunkBody = packed record - ChildCount, - NodeHeight: Cardinal; - States: TVirtualNodeStates; - Align: Byte; - CheckState: TCheckState; - CheckType: TCheckType; - Reserved: Cardinal; - end; - - TBaseChunk = packed record - Header: TChunkHeader; - Body: TBaseChunkBody; - end; - - // Toggle animation modes. - TToggleAnimationMode = ( - tamScrollUp, - tamScrollDown, - tamNoScroll - ); - - // Internally used data for animations. - TToggleAnimationData = record - Window: HWND; // copy of the tree's window handle - DC: HDC; // the DC of the window to erase uncovered parts - Brush: HBRUSH; // the brush to be used to erase uncovered parts - R1, - R2: TRect; // animation rectangles - Mode1, - Mode2: TToggleAnimationMode; // animation modes - ScaleFactor: Double; // the factor between the missing step size when doing two animations - MissedSteps: Double; - end; - - TCanvasEx = class(TCanvas); - -const - MagicID: TMagicID = (#$2045, 'V', 'T', WideChar(VTTreeStreamVersion), ' ', #$2046); - - // chunk IDs - NodeChunk = 1; - BaseChunk = 2; // chunk containing node state, check state, child node count etc. - // this chunk is immediately followed by all child nodes - CaptionChunk = 3; // used by the string tree to store a node's caption - UserChunk = 4; // used for data supplied by the application - - RTLFlag: array[Boolean] of Integer = (0, ETO_RTLREADING); - AlignmentToDrawFlag: array[TAlignment] of Cardinal = (DT_LEFT, DT_RIGHT, DT_CENTER); - - WideCR = Char(#13); - WideLF = Char(#10); - -var - gWatcher: TCriticalSection = nil; - gInitialized: Integer = 0; // >0 if global structures have been initialized; otherwise 0 - NeedToUnitialize: Boolean = False; // True if the OLE subsystem could be initialized successfully. - - -//---------------------------------------------------------------------------------------------------------------------- - -procedure ShowError(const Msg: string; HelpContext: Integer); - -begin - raise EVirtualTreeError.CreateHelp(Msg, HelpContext); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TreeFromNode(Node: PVirtualNode): TBaseVirtualTree; - -// Returns the tree the node currently belongs to or nil if the node is not attached to a tree. - -begin - Assert(Assigned(Node), 'Node must not be nil.'); - - // The root node is marked by having its NextSibling (and PrevSibling) pointing to itself. - while Assigned(Node) and (Node.NextSibling <> Node) do - Node := Node.Parent; - if Assigned(Node) then - Result := TBaseVirtualTree(Node.Parent) - else - Result := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -/// Wrapper function for styles services that handles differences between RAD Studio 10.4 and older versions, -/// as well as the case if these controls are used inside the IDE. -function VTStyleServices(AControl: TControl = nil): TCustomStyleServices; -begin - if Assigned(VTStyleServicesFunc) then - Result := VTStyleServicesFunc(AControl) - else - Result := Vcl.Themes.StyleServices{$if CompilerVersion >= 34}(AControl){$ifend}; -end; - -//---------------------------------------------------------------------------------------------------------------------- - - -procedure QuickSort(const TheArray: TNodeArray; L, R: Integer); - -var - I, J: Integer; - P, T: Pointer; - -begin - repeat - I := L; - J := R; - P := TheArray[(L + R) shr 1]; - repeat - while PAnsiChar(TheArray[I]) < PAnsiChar(P) do - Inc(I); - while PAnsiChar(TheArray[J]) > PAnsiChar(P) do - Dec(J); - if I <= J then - begin - T := TheArray[I]; - TheArray[I] := TheArray[J]; - TheArray[J] := T; - Inc(I); - Dec(J); - end; - until I > J; - if L < J then - QuickSort(TheArray, L, J); - L := I; - until I >= R; -end; - - -//---------------------------------------------------------------------------------------------------------------------- - - - - -const - Grays: array[0..3] of TColor = (clWhite, clSilver, clGray, clBlack); - SysGrays: array[0..3] of TColor = (clWindow, clBtnFace, clBtnShadow, clBtnText); - -procedure ConvertImageList(IL: TImageList; const ImageName: string; ColorRemapping: Boolean = True); - -// Loads a bunch of images given by ImageName into IL. If ColorRemapping = True then a mapping of gray values to -// system colors is performed. - -var - lImages, - lOneImage: TBitmap; - I: Integer; - MaskColor: TColor; - Source, - Dest: TRect; - -begin - gWatcher.Enter(); - try - // Since we want the image list appearing in the correct system colors, we have to remap its colors. - lImages := TBitmap.Create; - lOneImage := TBitmap.Create; - if ColorRemapping then - lImages.Handle := CreateMappedRes(FindClassHInstance(TBaseVirtualTree), PChar(ImageName), Grays, SysGrays) - else - lImages.Handle := LoadBitmap(FindClassHInstance(TBaseVirtualTree), PChar(ImageName)); - - try - Assert(lImages.Height > 0, 'Internal image "' + ImageName + '" is missing or corrupt.'); - if lImages.Height = 0 then - Exit;// This should never happen, it prevents a division by zero exception below in the for loop, which we have seen in a few cases - // It is assumed that the image height determines also the width of one entry in the image list. - IL.Clear; - IL.Height := lImages.Height; - IL.Width := lImages.Height; - lOneImage.Width := IL.Width; - lOneImage.Height := IL.Height; - MaskColor := lImages.Canvas.Pixels[0, 0]; // this is usually clFuchsia - Dest := Rect(0, 0, IL.Width, IL.Height); - for I := 0 to (lImages.Width div lImages.Height) - 1 do - begin - Source := Rect(I * IL.Width, 0, (I + 1) * IL.Width, IL.Height); - lOneImage.Canvas.CopyRect(Dest, lImages.Canvas, Source); - IL.AddMasked(lOneImage, MaskColor); - end; - finally - lImages.Free; - lOneImage.Free; - end; - finally - gWatcher.Leave(); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function CreateSystemImageSet(pControl: TWinControl): TImageList; - -// Creates a system check image set. -// Note: the DarkCheckImages and FlatImages image lists must already be filled, as some images from them are copied here. - -const - MaskColor: TColor = clRed; - cFlags = ILC_COLOR32 or ILC_MASK; - -var - BM: TBitmap; - Theme: HTHEME; - Details: TThemedElementDetails; - - //--------------------------------------------------------------------------- - - // Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style) - function StyleServices: TCustomStyleServices; - begin - Result := VTStyleServices(pControl); - end; - - procedure AddSystemImage(IL: TImageList; Index: Integer); - const - States: array [0..19] of Integer = ( - RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDDISABLED, - RBS_CHECKEDNORMAL, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDDISABLED, - CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDDISABLED, - CBS_CHECKEDNORMAL, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDDISABLED, - CBS_MIXEDNORMAL, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDDISABLED); - var - ButtonState: Cardinal; - ButtonType: Cardinal; - - begin - BM.Canvas.FillRect(Rect(0, 0, BM.Width, BM.Height)); - if StyleServices.Enabled and StyleServices.IsSystemStyle then - begin - if Index < 8 then - Details.Part := BP_RADIOBUTTON - else - Details.Part := BP_CHECKBOX; - Details.State := States[Index]; - DrawThemeBackground(Theme, BM.Canvas.Handle, Details.Part, Details.State, Rect(0, 0, BM.Width, BM.Height), nil); - end - else - begin - if Index < 8 then - ButtonType := DFCS_BUTTONRADIO - else - ButtonType := DFCS_BUTTONCHECK; - if Index >= 16 then - ButtonType := ButtonType or DFCS_BUTTON3STATE; - - case Index mod 4 of - 0: - ButtonState := 0; - 1: - ButtonState := DFCS_HOT; - 2: - ButtonState := DFCS_PUSHED; - else - ButtonState := DFCS_INACTIVE; - end; - if Index in [4..7, 12..19] then - ButtonState := ButtonState or DFCS_CHECKED; -// if Flat then -// ButtonState := ButtonState or DFCS_FLAT; - DrawFrameControl(BM.Canvas.Handle, Rect(0, 0, BM.Width, BM.Height), DFC_BUTTON, ButtonType or ButtonState); - end; - IL.AddMasked(BM, MaskColor); - end; - - //--------------- end local functions --------------------------------------- - -const - cDefaultCheckboxSize = 13;// Used when no other value is available -var - I: Integer; - lSize: TSize; - Res: Boolean; -begin - BM := TBitmap.Create; // Create a temporary bitmap, which holds the intermediate images. - try - Res := False; - // Retrieve the checkbox image size, prefer theme if available, fall back to GetSystemMetrics() otherwise, but this returns odd results on Windows 8 and higher in high-dpi scenarios. - if StyleServices.Enabled then - if StyleServices.IsSystemStyle then - begin - if Assigned(pControl) then - Theme := OpenThemeData(pControl.Handle, 'BUTTON') - else - Theme := OpenThemeData(Application.Handle, 'BUTTON'); - Details := StyleServices.GetElementDetails(tbCheckBoxUncheckedNormal); - Res := GetThemePartSize(Theme, BM.Canvas.Handle, Details.Part, Details.State, nil, TS_TRUE, lSize) = S_OK; - end - else - Res := StyleServices.GetElementSize(BM.Canvas.Handle, StyleServices.GetElementDetails(tbCheckBoxUncheckedNormal), TElementSize.esActual, lSize {$IF CompilerVersion >= 34}, pControl.CurrentPPI{$IFEND}); - if not Res then begin - lSize := TSize.Create(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK)); - if lSize.cx = 0 then begin // error? (Should happen rarely only) - lSize.cx := MulDiv(cDefaultCheckboxSize, Screen.PixelsPerInch, USER_DEFAULT_SCREEN_DPI); - lSize.cy := lSize.cx; - end;// if - end;//if - - Result := TImageList.CreateSize(lSize.cx, lSize.cy); - Result.Handle := ImageList_Create(Result.Width, Result.Height, cFlags, 0, Result.AllocBy); - Result.Masked := True; - Result.BkColor := clWhite; - - // Make the bitmap the same size as the image list is to avoid problems when adding. - BM.SetSize(Result.Width, Result.Height); - BM.Canvas.Brush.Color := MaskColor; - BM.Canvas.Brush.Style := bsSolid; - BM.Canvas.FillRect(Rect(0, 0, BM.Width, BM.Height)); - Result.AddMasked(BM, MaskColor); - - // Add the 20 system checkbox and radiobutton images. - for I := 0 to 19 do - AddSystemImage(Result, I); - if StyleServices.Enabled and StyleServices.IsSystemStyle then - CloseThemeData(Theme); - - finally - BM.Free; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - - - - - -procedure InitializeGlobalStructures(); - -// initialization of stuff global to the unit -begin - if (gInitialized > 0) or (InterlockedIncrement(gInitialized) <> 1) then // Ensure threadsafe that this code is executed only once - exit; - - // This watcher is used whenever a global structure could be modified by more than one thread. - gWatcher := TCriticalSection.Create(); - - IsWinVistaOrAbove := (Win32MajorVersion >= 6); - - // Initialize OLE subsystem for drag'n drop and clipboard operations. - NeedToUnitialize := not IsLibrary and Succeeded(OleInitialize(nil)); - - // Register the tree reference clipboard format. Others will be handled in InternalClipboarFormats. - CF_VTREFERENCE := RegisterClipboardFormat(CFSTR_VTREFERENCE); - - // Delphi (at least version 6 and lower) does not provide a standard split cursor. - // Hence we have to load our own. - Screen.Cursors[crHeaderSplit] := LoadCursor(HInstance, 'VT_HEADERSPLIT'); - Screen.Cursors[crVertSplit] := LoadCursor(HInstance, 'VT_VERTSPLIT'); - - // Clipboard format registration. - // Native clipboard format. Needs a new identifier and has an average priority to allow other formats to take over. - // This format is supposed to use the IStream storage format but unfortunately this does not work when - // OLEFlushClipboard is used. Hence it is disabled until somebody finds a solution. - CF_VIRTUALTREE := RegisterVTClipboardFormat(CFSTR_VIRTUALTREE, TBaseVirtualTree, 50, TYMED_HGLOBAL {or TYMED_ISTREAM}); - // Specialized string tree formats. - CF_HTML := RegisterVTClipboardFormat(CFSTR_HTML, TCustomVirtualStringTree, 80); - CF_VRTFNOOBJS := RegisterVTClipboardFormat(CFSTR_RTFNOOBJS, TCustomVirtualStringTree, 84); - CF_VRTF := RegisterVTClipboardFormat(CFSTR_RTF, TCustomVirtualStringTree, 85); - CF_CSV := RegisterVTClipboardFormat(CFSTR_CSV, TCustomVirtualStringTree, 90); - // Predefined clipboard formats. Just add them to the internal list. - RegisterVTClipboardFormat(CF_TEXT, TCustomVirtualStringTree, 100); - RegisterVTClipboardFormat(CF_UNICODETEXT, TCustomVirtualStringTree, 95); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure FinalizeGlobalStructures(); - -var - HintWasEnabled: Boolean; - -begin - if gInitialized = 0 then - exit; // Was not initialized - - if NeedToUnitialize then - OleUninitialize; - - // If VT is used in a package and its special hint window was used then the last instance of this - // window is not freed correctly (bug in the VCL). We explicitely tell the application to free it - // otherwise an AV is raised due to access to an invalid memory area. - if ModuleIsPackage then - begin - HintWasEnabled := Application.ShowHint; - Application.ShowHint := False; - if HintWasEnabled then - Application.ShowHint := True; - end; - gWatcher.Free; - gWatcher := nil; -end; - - - - -//----------------- TCustomVirtualTreeOptions -------------------------------------------------------------------------- - -constructor TCustomVirtualTreeOptions.Create(AOwner: TBaseVirtualTree); - -begin - FOwner := AOwner; - - FPaintOptions := DefaultPaintOptions; - FAnimationOptions := DefaultAnimationOptions; - FAutoOptions := DefaultAutoOptions; - FSelectionOptions := DefaultSelectionOptions; - FMiscOptions := DefaultMiscOptions; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomVirtualTreeOptions.SetAnimationOptions(const Value: TVTAnimationOptions); - -begin - FAnimationOptions := Value; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomVirtualTreeOptions.SetAutoOptions(const Value: TVTAutoOptions); - -var - ChangedOptions: TVTAutoOptions; - -begin - if FAutoOptions <> Value then - begin - // Exclusive ORing to get all entries wich are in either set but not in both. - ChangedOptions := FAutoOptions + Value - (FAutoOptions * Value); - FAutoOptions := Value; - with FOwner do - if (toAutoSpanColumns in ChangedOptions) and not (csLoading in ComponentState) and HandleAllocated then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomVirtualTreeOptions.InternalSetMiscOptions(const Value: TVTMiscOptions); -begin - FMiscOptions := value; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomVirtualTreeOptions.SetMiscOptions(const Value: TVTMiscOptions); - -var - ToBeSet, - ToBeCleared: TVTMiscOptions; - -begin - if FMiscOptions <> Value then - begin - ToBeSet := Value - FMiscOptions; - ToBeCleared := FMiscOptions - Value; - FMiscOptions := Value; - - with FOwner do - if not (csLoading in ComponentState) and HandleAllocated then - begin - if toCheckSupport in ToBeSet + ToBeCleared then - Invalidate; - if toEditOnDblClick in ToBeSet then - FMiscOptions := FMiscOptions - [toToggleOnDblClick]; // In order for toEditOnDblClick to take effect, we need to remove toToggleOnDblClick which is handled with priority. See issue #747 - - if not (csDesigning in ComponentState) then - begin - if toAcceptOLEDrop in ToBeCleared then - RevokeDragDrop(Handle); - if toFullRepaintOnResize in ToBeSet + ToBeCleared then - RecreateWnd; - if toAcceptOLEDrop in ToBeSet then - RegisterDragDrop(Handle, DragManager as IDropTarget); - if toVariableNodeHeight in ToBeSet then begin - BeginUpdate(); - try - ReInitNode(nil, True); - finally - EndUpdate(); - end;//try..finally - end;//if toVariableNodeHeight - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomVirtualTreeOptions.SetPaintOptions(const Value: TVTPaintOptions); - -var - ToBeSet, - ToBeCleared: TVTPaintOptions; - Run: PVirtualNode; - HandleWasAllocated: Boolean; - -begin - if FPaintOptions <> Value then - begin - ToBeSet := Value - FPaintOptions; - ToBeCleared := FPaintOptions - Value; - FPaintOptions := Value; - if (toFixedIndent in ToBeSet) then - begin - // Fixes issue #388 - Include(FPaintOptions, toShowRoot); - Include(ToBeSet, toShowRoot); - end;//if - with FOwner do - begin - HandleWasAllocated := HandleAllocated; - - if not (csLoading in ComponentState) and (toShowFilteredNodes in ToBeSet + ToBeCleared) then - begin - if HandleWasAllocated then - BeginUpdate; - InterruptValidation; - Run := GetFirstNoInit; - while Assigned(Run) do - begin - if (vsFiltered in Run.States) then - begin - if FullyVisible[Run] then - begin - if toShowFilteredNodes in ToBeSet then - Inc(FVisibleCount) - else - Dec(FVisibleCount); - end; - if toShowFilteredNodes in ToBeSet then - AdjustTotalHeight(Run, Run.NodeHeight, True) - else - AdjustTotalHeight(Run, -Run.NodeHeight, True); - end; - Run := GetNextNoInit(Run); - end; - if HandleWasAllocated then - EndUpdate; - end; - - if HandleAllocated then - begin - if IsWinVistaOrAbove and ((tsUseThemes in FStates) or - ((toThemeAware in ToBeSet) and StyleServices.Enabled)) and - (toUseExplorerTheme in (ToBeSet + ToBeCleared)) and not VclStyleEnabled then - begin - if (toUseExplorerTheme in ToBeSet) then - begin - SetWindowTheme('explorer'); - DoStateChange([tsUseExplorerTheme]); - end - else - if toUseExplorerTheme in ToBeCleared then - begin - SetWindowTheme(''); - DoStateChange([], [tsUseExplorerTheme]); - end; - end; - - if not (csLoading in ComponentState) then - begin - if ((toThemeAware in ToBeSet + ToBeCleared) or (toUseExplorerTheme in ToBeSet + ToBeCleared) or VclStyleEnabled) then - begin - if ((toThemeAware in ToBeSet) and StyleServices.Enabled) then - DoStateChange([tsUseThemes]) - else - if (toThemeAware in ToBeCleared) then - DoStateChange([], [tsUseThemes]); - - PrepareBitmaps(True, False); - RedrawWindow(Handle, nil, 0, RDW_INVALIDATE or RDW_VALIDATE or RDW_FRAME); - end; - - if toChildrenAbove in ToBeSet + ToBeCleared then - begin - InvalidateCache; - if FUpdateCount = 0 then - begin - ValidateCache; - Invalidate; - end; - end; - - Invalidate; - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomVirtualTreeOptions.SetSelectionOptions(const Value: TVTSelectionOptions); - -var - ToBeSet, - ToBeCleared: TVTSelectionOptions; - -begin - if FSelectionOptions <> Value then - begin - ToBeSet := Value - FSelectionOptions; - ToBeCleared := FSelectionOptions - Value; - FSelectionOptions := Value; - - with FOwner do - begin - if (toMultiSelect in (ToBeCleared + ToBeSet)) or - ([toLevelSelectConstraint, toSiblingSelectConstraint] * ToBeSet <> []) then - ClearSelection; - - if (toExtendedFocus in ToBeCleared) and (FFocusedColumn > 0) and HandleAllocated then - begin - FFocusedColumn := FHeader.MainColumn; - Invalidate; - end; - - if not (toExtendedFocus in FSelectionOptions) then - FFocusedColumn := FHeader.MainColumn; - end; - end; -end; - -function TCustomVirtualTreeOptions.StyleServices(AControl: TControl): TCustomStyleServices; -begin - Result := VTStyleServices(FOwner); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomVirtualTreeOptions.AssignTo(Dest: TPersistent); - -begin - if Dest is TCustomVirtualTreeOptions then - begin - with Dest as TCustomVirtualTreeOptions do - begin - PaintOptions := Self.PaintOptions; - AnimationOptions := Self.AnimationOptions; - AutoOptions := Self.AutoOptions; - SelectionOptions := Self.SelectionOptions; - MiscOptions := Self.MiscOptions; - end; - end - else - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -// OLE drag and drop support classes -// This is quite heavy stuff (compared with the VCL implementation) but is much better suited to fit the needs -// of DD'ing various kinds of virtual data and works also between applications. - -//----------------- TEnumFormatEtc ------------------------------------------------------------------------------------- - -constructor TEnumFormatEtc.Create(Tree: TBaseVirtualTree; const AFormatEtcArray: TFormatEtcArray); - -var - I: Integer; - -begin - inherited Create; - - FTree := Tree; - // Make a local copy of the format data. - SetLength(FFormatEtcArray, Length(AFormatEtcArray)); - for I := 0 to High(AFormatEtcArray) do - FFormatEtcArray[I] := AFormatEtcArray[I]; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TEnumFormatEtc.Clone(out Enum: IEnumFormatEtc): HResult; - -var - AClone: TEnumFormatEtc; - -begin - Result := S_OK; - try - AClone := TEnumFormatEtc.Create(nil, FFormatEtcArray); - AClone.FCurrentIndex := FCurrentIndex; - Enum := AClone as IEnumFormatEtc; - except - Result := E_FAIL; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TEnumFormatEtc.Next(celt: Integer; out elt; pceltFetched: PLongint): HResult; - -var - CopyCount: Integer; - -begin - Result := S_FALSE; - CopyCount := Length(FFormatEtcArray) - FCurrentIndex; - if celt < CopyCount then - CopyCount := celt; - if CopyCount > 0 then - begin - Move(FFormatEtcArray[FCurrentIndex], elt, CopyCount * SizeOf(TFormatEtc)); - Inc(FCurrentIndex, CopyCount); - Result := S_OK; - end; - if Assigned(pceltFetched) then - pceltFetched^ := CopyCount; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TEnumFormatEtc.Reset: HResult; - -begin - FCurrentIndex := 0; - Result := S_OK; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TEnumFormatEtc.Skip(celt: Integer): HResult; - -begin - if FCurrentIndex + celt < High(FFormatEtcArray) then - begin - Inc(FCurrentIndex, celt); - Result := S_Ok; - end - else - Result := S_FALSE; -end; - -//----------------- TVTDataObject -------------------------------------------------------------------------------------- - -constructor TVTDataObject.Create(AOwner: TBaseVirtualTree; ForClipboard: Boolean); - -begin - inherited Create; - - FOwner := AOwner; - FForClipboard := ForClipboard; - FOwner.GetNativeClipboardFormats(FFormatEtcArray); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -destructor TVTDataObject.Destroy; - -var - I: Integer; - StgMedium: PStgMedium; - -begin - // Cancel a pending clipboard operation if this data object was created for the clipboard and - // is freed because something else is placed there. - if FForClipboard and not (tsClipboardFlushing in FOwner.TreeStates) then - FOwner.CancelCutOrCopy; - - // Release any internal clipboard formats - for I := 0 to High(FormatEtcArray) do - begin - StgMedium := FindInternalStgMedium(FormatEtcArray[I].cfFormat); - if Assigned(StgMedium) then - ReleaseStgMedium(StgMedium^); - end; - - FormatEtcArray := nil; - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.CanonicalIUnknown(const TestUnknown: IUnknown): IUnknown; - -// Uses COM object identity: An explicit call to the IUnknown::QueryInterface method, requesting the IUnknown -// interface, will always return the same pointer. - -begin - if Assigned(TestUnknown) then - begin - if TestUnknown.QueryInterface(IUnknown, Result) = 0 then - Result._Release // Don't actually need it just need the pointer value - else - Result := TestUnknown; - end - else - Result := TestUnknown; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.EqualFormatEtc(FormatEtc1, FormatEtc2: TFormatEtc): Boolean; - -begin - Result := (FormatEtc1.cfFormat = FormatEtc2.cfFormat) and (FormatEtc1.ptd = FormatEtc2.ptd) and - (FormatEtc1.dwAspect = FormatEtc2.dwAspect) and (FormatEtc1.lindex = FormatEtc2.lindex) and - (FormatEtc1.tymed and FormatEtc2.tymed <> 0); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.FindFormatEtc(TestFormatEtc: TFormatEtc; const FormatEtcArray: TFormatEtcArray): integer; - -var - I: integer; - -begin - Result := -1; - for I := 0 to High(FormatEtcArray) do - begin - if EqualFormatEtc(TestFormatEtc, FormatEtcArray[I]) then - begin - Result := I; - Break; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.FindInternalStgMedium(Format: TClipFormat): PStgMedium; - -var - I: integer; -begin - Result := nil; - for I := 0 to High(InternalStgMediumArray) do - begin - if Format = InternalStgMediumArray[I].Format then - begin - Result := @InternalStgMediumArray[I].Medium; - Break; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.HGlobalClone(HGlobal: THandle): THandle; - -// Returns a global memory block that is a copy of the passed memory block. - -var - Size: Cardinal; - Data, - NewData: PByte; - -begin - Size := GlobalSize(HGlobal); - Result := GlobalAlloc(GPTR, Size); - Data := GlobalLock(hGlobal); - try - NewData := GlobalLock(Result); - try - Move(Data^, NewData^, Size); - finally - GlobalUnLock(Result); - end; - finally - GlobalUnLock(hGlobal); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.RenderInternalOLEData(const FormatEtcIn: TFormatEtc; var Medium: TStgMedium; - var OLEResult: HResult): Boolean; - -// Tries to render one of the formats which have been stored via the SetData method. -// Since this data is already there it is just copied or its reference count is increased (depending on storage medium). - -var - InternalMedium: PStgMedium; - -begin - Result := True; - InternalMedium := FindInternalStgMedium(FormatEtcIn.cfFormat); - if Assigned(InternalMedium) then - OLEResult := StgMediumIncRef(InternalMedium^, Medium, False, Self as IDataObject) - else - Result := False; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.StgMediumIncRef(const InStgMedium: TStgMedium; var OutStgMedium: TStgMedium; - CopyInMedium: Boolean; const DataObject: IDataObject): HRESULT; - -// InStgMedium is the data that is requested, OutStgMedium is the data that we are to return either a copy of or -// increase the IDataObject's reference and send ourselves back as the data (unkForRelease). The InStgMedium is usually -// the result of a call to find a particular FormatEtc that has been stored locally through a call to SetData. -// If CopyInMedium is not true we already have a local copy of the data when the SetData function was called (during -// that call the CopyInMedium must be true). Then as the caller asks for the data through GetData we do not have to make -// copy of the data for the caller only to have them destroy it then need us to copy it again if necessary. -// This way we increase the reference count to ourselves and pass the STGMEDIUM structure initially stored in SetData. -// This way when the caller frees the structure it sees the unkForRelease is not nil and calls Release on the object -// instead of destroying the actual data. - -var - Len: Integer; - -begin - Result := S_OK; - - // Simply copy all fields to start with. - OutStgMedium := InStgMedium; - // The data handled here always results from a call of SetData we got. This ensures only one storage format - // is indicated and hence the case statement below is safe (IDataObject.GetData can optionally use several - // storage formats). - case InStgMedium.tymed of - TYMED_HGLOBAL: - begin - if CopyInMedium then - begin - // Generate a unique copy of the data passed - OutStgMedium.hGlobal := HGlobalClone(InStgMedium.hGlobal); - if OutStgMedium.hGlobal = 0 then - Result := E_OUTOFMEMORY; - end - else - // Don't generate a copy just use ourselves and the copy previously saved. - OutStgMedium.unkForRelease := Pointer(DataObject); // Does not increase RefCount. - end; - TYMED_FILE: - begin - Len := lstrLenW(InStgMedium.lpszFileName) + 1; // Don't forget the terminating null character. - OutStgMedium.lpszFileName := CoTaskMemAlloc(2 * Len); - Move(InStgMedium.lpszFileName^, OutStgMedium.lpszFileName^, 2 * Len); - end; - TYMED_ISTREAM: - IUnknown(OutStgMedium.stm)._AddRef; - TYMED_ISTORAGE: - IUnknown(OutStgMedium.stg)._AddRef; - TYMED_GDI: - if not CopyInMedium then - // Don't generate a copy just use ourselves and the previously saved data. - OutStgMedium.unkForRelease := Pointer(DataObject) // Does not increase RefCount. - else - Result := DV_E_TYMED; // Don't know how to copy GDI objects right now. - TYMED_MFPICT: - if not CopyInMedium then - // Don't generate a copy just use ourselves and the previously saved data. - OutStgMedium.unkForRelease := Pointer(DataObject) // Does not increase RefCount. - else - Result := DV_E_TYMED; // Don't know how to copy MetaFile objects right now. - TYMED_ENHMF: - if not CopyInMedium then - // Don't generate a copy just use ourselves and the previously saved data. - OutStgMedium.unkForRelease := Pointer(DataObject) // Does not increase RefCount. - else - Result := DV_E_TYMED; // Don't know how to copy enhanced metafiles objects right now. - else - Result := DV_E_TYMED; - end; - - if (Result = S_OK) and Assigned(OutStgMedium.unkForRelease) then - IUnknown(OutStgMedium.unkForRelease)._AddRef; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.DAdvise(const FormatEtc: TFormatEtc; advf: Integer; const advSink: IAdviseSink; - out dwConnection: Integer): HResult; - -// Advise sink management is greatly simplified by the IDataAdviseHolder interface. -// We use this interface and forward all concerning calls to it. - -begin - Result := S_OK; - if FAdviseHolder = nil then - Result := CreateDataAdviseHolder(FAdviseHolder); - if Result = S_OK then - Result := FAdviseHolder.Advise(Self as IDataObject, FormatEtc, advf, advSink, dwConnection); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.DUnadvise(dwConnection: Integer): HResult; - -begin - if FAdviseHolder = nil then - Result := E_NOTIMPL - else - Result := FAdviseHolder.Unadvise(dwConnection); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.EnumDAdvise(out enumAdvise: IEnumStatData): HResult; - -begin - if FAdviseHolder = nil then - Result := OLE_E_ADVISENOTSUPPORTED - else - Result := FAdviseHolder.EnumAdvise(enumAdvise); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.EnumFormatEtc(Direction: Integer; out EnumFormatEtc: IEnumFormatEtc): HResult; - -var - NewList: TEnumFormatEtc; - -begin - Result := E_FAIL; - if Direction = DATADIR_GET then - begin - NewList := TEnumFormatEtc.Create(FOwner, FormatEtcArray); - EnumFormatEtc := NewList as IEnumFormatEtc; - Result := S_OK; - end - else - EnumFormatEtc := nil; - if EnumFormatEtc = nil then - Result := OLE_S_USEREG; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.GetCanonicalFormatEtc(const FormatEtc: TFormatEtc; out FormatEtcOut: TFormatEtc): HResult; - -begin - Result := DATA_S_SAMEFORMATETC; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.GetData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium): HResult; - -// Data is requested by clipboard or drop target. This method dispatchs the call -// depending on the data being requested. - -var - I: Integer; - Data: PVTReference; - -begin - // The tree reference format is always supported and returned from here. - if FormatEtcIn.cfFormat = CF_VTREFERENCE then - begin - // Note: this format is not used while flushing the clipboard to avoid a dangling reference - // when the owner tree is destroyed before the clipboard data is replaced with something else. - if tsClipboardFlushing in FOwner.TreeStates then - Result := E_FAIL - else - begin - Medium.hGlobal := GlobalAlloc(GHND or GMEM_SHARE, SizeOf(TVTReference)); - Data := GlobalLock(Medium.hGlobal); - Data.Process := GetCurrentProcessID; - Data.Tree := FOwner; - GlobalUnlock(Medium.hGlobal); - Medium.tymed := TYMED_HGLOBAL; - Medium.unkForRelease := nil; - Result := S_OK; - end; - end - else - begin - try - // See if we accept this type and if not get the correct return value. - Result := QueryGetData(FormatEtcIn); - if Result = S_OK then - begin - for I := 0 to High(FormatEtcArray) do - begin - if EqualFormatEtc(FormatEtcIn, FormatEtcArray[I]) then - begin - if not RenderInternalOLEData(FormatEtcIn, Medium, Result) then - Result := FOwner.RenderOLEData(FormatEtcIn, Medium, FForClipboard); - Break; - end; - end; - end; - except - ZeroMemory (@Medium, SizeOf(Medium)); - Result := E_FAIL; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.GetDataHere(const FormatEtc: TFormatEtc; out Medium: TStgMedium): HResult; - -begin - Result := E_NOTIMPL; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.QueryGetData(const FormatEtc: TFormatEtc): HResult; - -var - I: Integer; - -begin - Result := DV_E_CLIPFORMAT; - for I := 0 to High(FFormatEtcArray) do - begin - if FormatEtc.cfFormat = FFormatEtcArray[I].cfFormat then - begin - if (FormatEtc.tymed and FFormatEtcArray[I].tymed) <> 0 then - begin - if FormatEtc.dwAspect = FFormatEtcArray[I].dwAspect then - begin - if FormatEtc.lindex = FFormatEtcArray[I].lindex then - begin - Result := S_OK; - Break; - end - else - Result := DV_E_LINDEX; - end - else - Result := DV_E_DVASPECT; - end - else - Result := DV_E_TYMED; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDataObject.SetData(const FormatEtc: TFormatEtc; var Medium: TStgMedium; DoRelease: BOOL): HResult; - -// Allows dynamic adding to the IDataObject during its existance. Most noteably it is used to implement -// IDropSourceHelper and allows to set a special format for optimized moves during a shell transfer. - -var - Index: Integer; - LocalStgMedium: PStgMedium; - -begin - // See if we already have a format of that type available. - Index := FindFormatEtc(FormatEtc, FormatEtcArray); - if Index > - 1 then - begin - // Just use the TFormatEct in the array after releasing the data. - LocalStgMedium := FindInternalStgMedium(FormatEtcArray[Index].cfFormat); - if Assigned(LocalStgMedium) then - begin - ReleaseStgMedium(LocalStgMedium^); - ZeroMemory(LocalStgMedium, SizeOf(LocalStgMedium^)); - end; - end - else - begin - // It is a new format so create a new TFormatCollectionItem, copy the - // FormatEtc parameter into the new object and and put it in the list. - SetLength(FFormatEtcArray, Length(FormatEtcArray) + 1); - FormatEtcArray[High(FormatEtcArray)] := FormatEtc; - - // Create a new InternalStgMedium and initialize it and associate it with the format. - SetLength(FInternalStgMediumArray, Length(InternalStgMediumArray) + 1); - InternalStgMediumArray[High(InternalStgMediumArray)].Format := FormatEtc.cfFormat; - LocalStgMedium := @InternalStgMediumArray[High(InternalStgMediumArray)].Medium; - ZeroMemory(LocalStgMedium, SizeOf(LocalStgMedium^)); - end; - - if DoRelease then - begin - // We are simply being given the data and we take control of it. - LocalStgMedium^ := Medium; - Result := S_OK; - end - else - begin - // We need to reference count or copy the data and keep our own references to it. - Result := StgMediumIncRef(Medium, LocalStgMedium^, True, Self as IDataObject); - - // Can get a circular reference if the client calls GetData then calls SetData with the same StgMedium. - // Because the unkForRelease for the IDataObject can be marshalled it is necessary to get pointers that - // can be correctly compared. See the IDragSourceHelper article by Raymond Chen at MSDN. - if Assigned(LocalStgMedium.unkForRelease) then - begin - if CanonicalIUnknown(Self) = CanonicalIUnknown(IUnknown(LocalStgMedium.unkForRelease)) then - IUnknown(LocalStgMedium.unkForRelease) := nil; // release the interface - end; - end; - - // Tell all registered advice sinks about the data change. - if Assigned(FAdviseHolder) then - FAdviseHolder.SendOnDataChange(Self as IDataObject, 0, 0); -end; - -//----------------- TVTDragManager ------------------------------------------------------------------------------------- - -constructor TVTDragManager.Create(AOwner: TBaseVirtualTree); - -begin - inherited Create; - FOwner := AOwner; - - // Create an instance of the drop target helper interface. This will fail but not harm on systems which do - // not support this interface (everything below Windows 2000); - CoCreateInstance(CLSID_DragDropHelper, nil, CLSCTX_INPROC_SERVER, IID_IDropTargetHelper, FDropTargetHelper); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -destructor TVTDragManager.Destroy; - -begin - // Set the owner's reference to us to nil otherwise it will access an invalid pointer - // after our desctruction is complete. - FOwner.ClearDragManager; - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragManager.GetDataObject: IDataObject; - -begin - // When the owner tree starts a drag operation then it gets a data object here to pass it to the OLE subsystem. - // In this case there is no local reference to a data object and one is created (but not stored). - // If there is a local reference then the owner tree is currently the drop target and the stored interface is - // that of the drag initiator. - if Assigned(FDataObject) then - Result := FDataObject - else - begin - Result := FOwner.DoCreateDataObject; - if (Result = nil) and not Assigned(FOwner.OnCreateDataObject) then - // Do not create a TVTDataObject if the event handler explicitely decided not to supply one, issue #736. - Result := TVTDataObject.Create(FOwner, False) as IDataObject; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragManager.GetDragSource: TBaseVirtualTree; - -begin - Result := FDragSource; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragManager.GetDropTargetHelperSupported: Boolean; - -begin - Result := Assigned(FDropTargetHelper); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragManager.GetIsDropTarget: Boolean; - -begin - Result := FIsDropTarget; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragManager.DragEnter(const DataObject: IDataObject; KeyState: Integer; Pt: TPoint; - var Effect: Integer): HResult; - -begin - FDataObject := DataObject; - FIsDropTarget := True; - - SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, @FFullDragging, 0); - // If full dragging of window contents is disabled in the system then our tree windows will be locked - // and cannot be updated during a drag operation. With the following call painting is again enabled. - if not FFullDragging then - LockWindowUpdate(0); - if Assigned(FDropTargetHelper) and FFullDragging then begin - if toAutoScroll in Self.FOwner.TreeOptions.AutoOptions then - FDropTargetHelper.DragEnter(FOwner.Handle, DataObject, Pt, Effect) - else - FDropTargetHelper.DragEnter(0, DataObject, Pt, Effect);// Do not pass handle, otherwise the IDropTargetHelper will perform autoscroll. Issue #486 - end; - FDragSource := FOwner.GetTreeFromDataObject(DataObject); - Result := FOwner.DragEnter(KeyState, Pt, Effect); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragManager.DragLeave: HResult; - -begin - if Assigned(FDropTargetHelper) and FFullDragging then - FDropTargetHelper.DragLeave; - - FOwner.DragLeave; - FIsDropTarget := False; - FDragSource := nil; - FDataObject := nil; - Result := NOERROR; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragManager.DragOver(KeyState: Integer; Pt: TPoint; var Effect: Integer): HResult; - -begin - if Assigned(FDropTargetHelper) and FFullDragging then - FDropTargetHelper.DragOver(Pt, Effect); - - Result := FOwner.DragOver(FDragSource, KeyState, dsDragMove, Pt, Effect); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragManager.Drop(const DataObject: IDataObject; KeyState: Integer; Pt: TPoint; - var Effect: Integer): HResult; - -begin - if Assigned(FDropTargetHelper) and FFullDragging then - FDropTargetHelper.Drop(DataObject, Pt, Effect); - - Result := FOwner.DragDrop(DataObject, KeyState, Pt, Effect); - FIsDropTarget := False; - FDataObject := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTDragManager.ForceDragLeave; - -// Some drop targets, e.g. Internet Explorer leave a drag image on screen instead removing it when they receive -// a drop action. This method calls the drop target helper's DragLeave method to ensure it removes the drag image from -// screen. Unfortunately, sometimes not even this does help (e.g. when dragging text from VT to a text field in IE). - -begin - if Assigned(FDropTargetHelper) and FFullDragging then - FDropTargetHelper.DragLeave; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragManager.GiveFeedback(Effect: Integer): HResult; - -begin - Result := DRAGDROP_S_USEDEFAULTCURSORS; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragManager.QueryContinueDrag(EscapePressed: BOOL; KeyState: Integer): HResult; - -var - RButton, - LButton: Boolean; - -begin - LButton := (KeyState and MK_LBUTTON) <> 0; - RButton := (KeyState and MK_RBUTTON) <> 0; - - // Drag'n drop canceled by pressing both mouse buttons or Esc? - if (LButton and RButton) or EscapePressed then - Result := DRAGDROP_S_CANCEL - else - // Drag'n drop finished? - if not (LButton or RButton) then - Result := DRAGDROP_S_DROP - else - Result := S_OK; -end; - - -//----------------- TVirtualTreeHintWindow ----------------------------------------------------------------------------- - -procedure TVirtualTreeHintWindow.CMTextChanged(var Message: TMessage); - -begin - // swallow this message to prevent the ancestor from resizing the window (we don't use the caption anyway) -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeHintWindow.WMEraseBkgnd(var Message: TWMEraseBkgnd); - -// The control is fully painted by own code so don't erase its background as this causes flickering. - -begin - Message.Result := 1; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeHintWindow.CreateParams(var Params: TCreateParams); - -begin - inherited CreateParams(Params); - - with Params do - begin - Style := WS_POPUP; - ExStyle := ExStyle and not WS_EX_CLIENTEDGE; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeHintWindow.Paint(); -var - R: TRect; - Y: Integer; - S: string; - DrawFormat: Cardinal; - HintKind: TVTHintKind; - LClipRect: TRect; - - LColor: TColor; - LDetails: TThemedElementDetails; - LGradientStart: TColor; - LGradientEnd: TColor; - -begin - with FHintData do - begin - // Do actual painting only in the very first run. - // If the given node is nil then we have to display a header hint. - if (Node = nil) or (Tree.FHintMode <> hmToolTip) then - begin - Canvas.Font := Screen.HintFont; - Canvas.Font.Height := MulDiv(Canvas.Font.Height, Tree.ScaledPixels(96), Screen.PixelsPerInch); // See issue #992 - Y := 2; - end - else - begin - Tree.GetTextInfo(Node, Column, Canvas.Font, R, S); - if LineBreakStyle = hlbForceMultiLine then - Y := 1 - else - Y := (R.Top - R.Bottom + Self.Height) div 2; - end; - - R := Rect(0, 0, Width, Height); - - HintKind := vhkText; - if Assigned(Node) then - Tree.DoGetHintKind(Node, Column, HintKind); - - if HintKind = vhkOwnerDraw then - begin - Tree.DoDrawHint(Canvas, Node, R, Column); - end - else - with Canvas do - begin - if Tree.VclStyleEnabled then - begin - InflateRect(R, -1, -1); // Fixes missing border when VCL styles are used - LDetails := StyleServices(Tree).GetElementDetails(thHintNormal); - if StyleServices(Tree).GetElementColor(LDetails, ecGradientColor1, LColor) and (LColor <> clNone) then - LGradientStart := LColor - else - LGradientStart := clInfoBk; - if StyleServices(Tree).GetElementColor(LDetails, ecGradientColor2, LColor) and (LColor <> clNone) then - LGradientEnd := LColor - else - LGradientEnd := clInfoBk; - if StyleServices(Tree).GetElementColor(LDetails, ecTextColor, LColor) and (LColor <> clNone) then - Font.Color := LColor - else - Font.Color := Screen.HintFont.Color; - GradientFillCanvas(Canvas, LGradientStart, LGradientEnd, R, gdVertical); - end - else - begin - // Still force tooltip back and text color. - Font.Color := clInfoText; - Pen.Color := clBlack; - Brush.Color := clInfoBk; - if IsWinVistaOrAbove and StyleServices(Tree).Enabled and ((toThemeAware in Tree.TreeOptions.PaintOptions) or - (toUseExplorerTheme in Tree.TreeOptions.PaintOptions)) then - begin - if toUseExplorerTheme in Tree.TreeOptions.PaintOptions then // ToolTip style - StyleServices(Tree).DrawElement(Canvas.Handle, StyleServices(Tree).GetElementDetails(tttStandardNormal), R {$IF CompilerVersion >= 34}, nil, FCurrentPPI{$IFEND}) - else - begin // Hint style - LClipRect := R; - InflateRect(R, 4, 4); - StyleServices(Tree).DrawElement(Handle, StyleServices(Tree).GetElementDetails(tttStandardNormal), R, @LClipRect{$IF CompilerVersion >= 34}, FCurrentPPI{$IFEND}); - R := LClipRect; - StyleServices(Tree).DrawEdge(Handle, StyleServices(Tree).GetElementDetails(twWindowRoot), R, [eeRaisedOuter], [efRect]); - end; - end - else - if Tree.VclStyleEnabled then - StyleServices(Tree).DrawElement(Canvas.Handle, StyleServices(Tree).GetElementDetails(tttStandardNormal), R {$IF CompilerVersion >= 34}, nil, FCurrentPPI{$IFEND}) - else - Rectangle(R); - end; - // Determine text position and don't forget the border. - InflateRect(R, -1, -1); - DrawFormat := DT_TOP or DT_NOPREFIX; - SetBkMode(Handle, Winapi.Windows.TRANSPARENT); - R.Top := Y; - R.Left := R.Left + 3; // Make the text more centered - if Assigned(Node) and (LineBreakStyle = hlbForceMultiLine) then - DrawFormat := DrawFormat or DT_WORDBREAK; - Winapi.Windows.DrawTextW(Handle, PWideChar(HintText), Length(HintText), R, DrawFormat); - end; - end; -end; - -function TVirtualTreeHintWindow.StyleServices(AControl: TControl): TCustomStyleServices; -begin - Result := VTStyleServices(AControl); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect; - -var - TM: TTextMetric; - R: TRect; - -begin - try - if AData = nil then - // Defensive approach, it *can* happen that AData is nil. Maybe when several user defined hint classes are used. - Result := Rect(0, 0, 0, 0) - else - begin - // The hint window does not need any bidi mode setting but the caller of this method (TApplication.ActivateHint) - // does some unneccessary actions if the hint window is not left-to-right. - // The text alignment is based on the bidi mode passed in the hint data, hence we can - // simply set the window's mode to left-to-right (it might have been modified by the caller, if the - // tree window is right-to-left aligned). - BidiMode := bdLeftToRight; - - FHintData := PVTHintData(AData)^; - - with FHintData do - begin - // The draw tree gets its hint size by the application (but only if not a header hint is about to show). - // If the user will be drawing the hint, it gets its hint size by the application - // (but only if not a header hint is about to show). - // This size has already been determined in CMHintShow. - if Assigned(Node) and (not IsRectEmpty(HintRect)) then - Result := HintRect - else - begin - if Column <= NoColumn then - begin - BidiMode := Tree.BidiMode; - Alignment := Tree.Alignment; - end - else - begin - BidiMode := Tree.Header.Columns[Column].BidiMode; - Alignment := Tree.Header.Columns[Column].Alignment; - end; - - if BidiMode <> bdLeftToRight then - ChangeBidiModeAlignment(Alignment); - - if (Node = nil) or (Tree.FHintMode <> hmToolTip) then - begin - Canvas.Font := Screen.HintFont; - Canvas.Font.Height := MulDiv(Canvas.Font.Height, Tree.ScaledPixels(96), Screen.PixelsPerInch); // See issue #992 - end - else - begin - Canvas.Font := Tree.Font; - if Tree is TCustomVirtualStringTree then - with TCustomVirtualStringTree(Tree) do - DoPaintText(Node, Self.Canvas, Column, ttNormal); - end; - - GetTextMetrics(Canvas.Handle, TM); - FTextHeight := TM.tmHeight; - - if Length(HintText) = 0 then - Result := Rect(0, 0, 0, 0) - else - begin - if Assigned(Node) and (Tree.FHintMode = hmToolTip) then - begin - // Determine actual line break style depending on what was returned by the methods and what's in the node. - if LineBreakStyle = hlbDefault then - if vsMultiline in Node.States then - LineBreakStyle := hlbForceMultiLine - else - LineBreakStyle := hlbForceSingleLine; - - // Hint for a node. - if LineBreakStyle = hlbForceMultiLine then - begin - // Multiline tooltips use the columns width but extend the bottom border to fit the whole caption. - Result := Tree.GetDisplayRect(Node, Column, True, False); - R := Result; - - // On Windows NT/2K/XP the behavior of the tooltip is slightly different to that on Windows 9x/Me. - // We don't have Unicode word wrap on the latter so the tooltip gets as wide as the largest line - // in the caption (limited by carriage return), which results in unoptimal overlay of the tooltip. - Winapi.Windows.DrawTextW(Canvas.Handle, PWideChar(HintText), Length(HintText), R, DT_CALCRECT or DT_WORDBREAK); - if BidiMode = bdLeftToRight then - Result.Right := R.Right + Tree.FTextMargin - else - Result.Left := R.Left - Tree.FTextMargin + 1; - Result.Bottom := R.Bottom; - - Inc(Result.Right); - - // If the node height and the column width are both already large enough to cover the entire text, - // then we don't need the hint, though. - // However if the text is partially scrolled out of the client area then a hint is useful as well. - if (Tree.Header.Columns.Count > 0) and ((Integer(Tree.NodeHeight[Node]) + 2) >= (Result.Bottom - Result.Top)) and - ((Tree.Header.Columns[Column].Width + 2) >= (Result.Right - Result.Left)) and not - ((Result.Left < 0) or (Result.Right > Tree.ClientWidth + 3) or - (Result.Top < 0) or (Result.Bottom > Tree.ClientHeight + 3)) then - begin - Result := Rect(0, 0, 0, 0); - Exit; - end; - end - else - begin - Result := Tree.FLastHintRect; // = Tree.GetDisplayRect(Node, Column, True, True, True); see TBaseVirtualTree.CMHintShow - - { Fixes issue #623 - - Measure the rectangle to draw the text. The width of the result - is always adjusted according to the hint text because it may - be a custom hint coming in which can be larger or smaller than - the node text. - Earlier logic was using the current width of the node that was - either cutting off the hint text or producing undesired space - on the right. - } - R := Rect(0, 0, MaxWidth, FTextHeight); - Winapi.Windows.DrawTextW(Canvas.Handle, PWideChar(HintText), Length(HintText), R, DT_CALCRECT or DT_TOP or DT_NOPREFIX or DT_WORDBREAK); - if R.Right <> result.right - result.left then - begin - result.Right := result.Left + r.Right; - - //Space on right--taken from the code in the hmHint branch below. - if Assigned(Tree) then - Inc(Result.Right, Tree.FTextMargin + Tree.FMargin + Tree.ScaledPixels(4)); - end; - // Fix ends. - - if toShowHorzGridLines in Tree.TreeOptions.PaintOptions then - Dec(Result.Bottom); - end; - - // Include a one pixel border. - InflateRect(Result, 1, 1); - - // Make the coordinates relative. They will again be offset by the caller code. - OffsetRect(Result, -Result.Left - 1, -Result.Top - 1); - end - else - begin - // Hint for a header or non-tooltip hint. - - // Start with the base size of the hint in client coordinates. - Result := Rect(0, 0, MaxWidth, FTextHeight); - // Calculate the true size of the text rectangle. - Winapi.Windows.DrawTextW(Canvas.Handle, PWideChar(HintText), Length(HintText), Result, DT_CALCRECT or DT_TOP or DT_NOPREFIX or DT_WORDBREAK); - // The height of the text plus 2 pixels vertical margin plus the border determine the hint window height. - // Minus 4 because THintWindow.ActivateHint adds 4 to Rect.Bottom anyway. Note that it is not scaled because the RTL itself does not do any scaling either. - Inc(Result.Bottom, Tree.ScaledPixels(6) - 4); - // The text is centered horizontally with usual text margin for left and right borders (plus border). - if not Assigned(Tree) then - Exit; // Workaround, because we have seen several exceptions here caught by Eurekalog. Submitted as issue #114 to http://code.google.com/p/virtual-treeview/ - { Issue #623 Fix for strange space on the right. - Original logic was adding FTextHeight. Changed it to add FMargin instead and - it looks OK even if the hint font is larger. - } - Inc(Result.Right, Tree.FTextMargin - + Tree.FMargin + Tree.ScaledPixels(4)); //Issue #623 space on right - //+ FTextHeight); // Old code: We are extending the width here, but the text height scales with the text width and has a similar value as AveCharWdith * 2. - end; - end; - end; - end; - end; - except - Application.HandleException(Self); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeHintWindow.IsHintMsg(var Msg: TMsg): Boolean; - -// The VCL is a bit too generous when telling that an existing hint can be cancelled. Need to specify further here. - -begin - Result := inherited IsHintMsg(Msg) and HandleAllocated and IsWindowVisible(Handle); - // Avoid that mouse moves over the non-client area or cursor key presses cancel the current hint. - if Result and ((Msg.Message = WM_NCMOUSEMOVE) or ((Msg.Message >= WM_KEYFIRST) and (Msg.Message <= WM_KEYLAST) and (Msg.wparam in [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT]))) then - Result := False; -end; - -//----------------- TVTDragImage --------------------------------------------------------------------------------------- - -constructor TVTDragImage.Create(AOwner: TBaseVirtualTree); - -begin - FOwner := AOwner; - FTransparency := 128; - FPreBlendBias := 0; - FPostBlendBias := 0; - FFade := False; - FRestriction := dmrNone; - FColorKey := clNone; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -destructor TVTDragImage.Destroy; - -begin - EndDrag; - - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragImage.GetVisible: Boolean; - -// Returns True if the internal drag image is used (i.e. the system does not natively support drag images) and -// the internal image is currently visible on screen. - -begin - Result := FStates * [disHidden, disInDrag, disPrepared, disSystemSupport] = [disInDrag, disPrepared]; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTDragImage.InternalShowDragImage(ScreenDC: HDC); - -// Frequently called helper routine to actually do the blend and put it onto the screen. -// Only used if the system does not support drag images. - -var - BlendMode: TBlendMode; - -begin - with FAlphaImage do - BitBlt(Canvas.Handle, 0, 0, Width, Height, FBackImage.Canvas.Handle, 0, 0, SRCCOPY); - if not FFade and (FColorKey = clNone) then - BlendMode := bmConstantAlpha - else - BlendMode := bmMasterAlpha; - with FDragImage do - AlphaBlend(Canvas.Handle, FAlphaImage.Canvas.Handle, Rect(0, 0, Width, Height), Point(0, 0), BlendMode, - FTransparency, FPostBlendBias); - - with FAlphaImage do - BitBlt(ScreenDC, FImagePosition.X, FImagePosition.Y, Width, Height, Canvas.Handle, 0, 0, SRCCOPY); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTDragImage.MakeAlphaChannel(Source, Target: TBitmap); - -// Helper method to create a proper alpha channel in Target (which must be in 32 bit pixel format), depending -// on the settings for the drag image and the color values in Source. -// Only used if the system does not support drag images. - -type - PBGRA = ^TBGRA; - TBGRA = packed record - case Boolean of - False: - (Color: Cardinal); - True: - (BGR: array[0..2] of Byte; - Alpha: Byte); - end; - -var - Color, - ColorKeyRef: COLORREF; - UseColorKey: Boolean; - SourceRun, - TargetRun: PBGRA; - X, Y, - MaxDimension, - HalfWidth, - HalfHeight: Integer; - T: Extended; - -begin - UseColorKey := ColorKey <> clNone; - ColorKeyRef := ColorToRGB(ColorKey) and $FFFFFF; - // Color values are in the form BGR (red on LSB) while bitmap colors are in the form ARGB (blue on LSB) - // hence we have to swap red and blue in the color key. - with TBGRA(ColorKeyRef) do - begin - X := BGR[0]; - BGR[0] := BGR[2]; - BGR[2] := X; - end; - - with Target do - begin - MaxDimension := Max(Width, Height); - - HalfWidth := Width div 2; - HalfHeight := Height div 2; - for Y := 0 to Height - 1 do - begin - TargetRun := Scanline[Y]; - SourceRun := Source.Scanline[Y]; - for X := 0 to Width - 1 do - begin - Color := SourceRun.Color and $FFFFFF; - if UseColorKey and (Color = ColorKeyRef) then - TargetRun.Alpha := 0 - else - begin - // If the color is not the given color key (or none is used) then do full calculation of a bell curve. - T := Exp(-8 * Sqrt(Sqr((X - HalfWidth) / MaxDimension) + Sqr((Y - HalfHeight) / MaxDimension))); - TargetRun.Alpha := Round(255 * T); - end; - Inc(SourceRun); - Inc(TargetRun); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragImage.DragTo(P: TPoint; ForceRepaint: Boolean): Boolean; - -// Moves the drag image to a new position, which is determined from the passed point P and the previous -// mouse position. -// ForceRepaint is True if something on the screen changed and the back image must be refreshed. - -var - ScreenDC: HDC; - DeltaX, - DeltaY: Integer; - - // optimized drag image move support - RSamp1, - RSamp2, // newly added parts from screen which will be overwritten - RDraw1, - RDraw2, // parts to be restored to screen - RScroll, - RClip: TRect; // ScrollDC of the existent background - -begin - // Determine distances to move the drag image. Take care for restrictions. - case FRestriction of - dmrHorizontalOnly: - begin - DeltaX := FLastPosition.X - P.X; - DeltaY := 0; - end; - dmrVerticalOnly: - begin - DeltaX := 0; - DeltaY := FLastPosition.Y - P.Y; - end; - else // dmrNone - DeltaX := FLastPosition.X - P.X; - DeltaY := FLastPosition.Y - P.Y; - end; - - Result := (DeltaX <> 0) or (DeltaY <> 0) or ForceRepaint; - if Result then - begin - if Visible then - begin - // All this stuff is only called if we have to handle the drag image ourselves. If the system supports - // drag image then this is all never executed. - ScreenDC := GetDC(0); - try - if (Abs(DeltaX) >= FDragImage.Width) or (Abs(DeltaY) >= FDragImage.Height) or ForceRepaint then - begin - // If moved more than image size then just restore old screen and blit image to new position. - BitBlt(ScreenDC, FImagePosition.X, FImagePosition.Y, FBackImage.Width, FBackImage.Height, - FBackImage.Canvas.Handle, 0, 0, SRCCOPY); - - if ForceRepaint then - UpdateWindow(FOwner.Handle); - - Inc(FImagePosition.X, -DeltaX); - Inc(FImagePosition.Y, -DeltaY); - - BitBlt(FBackImage.Canvas.Handle, 0, 0, FBackImage.Width, FBackImage.Height, ScreenDC, FImagePosition.X, - FImagePosition.Y, SRCCOPY); - end - else - begin - // overlapping copy - FillDragRectangles(FDragImage.Width, FDragImage.Height, DeltaX, DeltaY, RClip, RScroll, RSamp1, RSamp2, RDraw1, - RDraw2); - - with FBackImage.Canvas do - begin - // restore uncovered areas of the screen - if DeltaX = 0 then - begin - with TWithSafeRect(RDraw2) do - BitBlt(ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, Right, Bottom, Handle, Left, Top, - SRCCOPY); - end - else - begin - if DeltaY = 0 then - begin - with TWithSafeRect(RDraw1) do - BitBlt(ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, Right, Bottom, Handle, Left, Top, - SRCCOPY); - end - else - begin - with TWithSafeRect(RDraw1) do - BitBlt(ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, Right, Bottom, Handle, Left, Top, - SRCCOPY); - with TWithSafeRect(RDraw2) do - BitBlt(ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, Right, Bottom, Handle, Left, Top, - SRCCOPY); - end; - end; - - // move existent background - ScrollDC(Handle, DeltaX, DeltaY, RScroll, RClip, 0, nil); - - Inc(FImagePosition.X, -DeltaX); - Inc(FImagePosition.Y, -DeltaY); - - // Get first and second additional rectangle from screen. - if DeltaX = 0 then - begin - with TWithSafeRect(RSamp2) do - BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, - SRCCOPY); - end - else - if DeltaY = 0 then - begin - with TWithSafeRect(RSamp1) do - BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, - SRCCOPY); - end - else - begin - with TWithSafeRect(RSamp1) do - BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, - SRCCOPY); - with TWithSafeRect(RSamp2) do - BitBlt(Handle, Left, Top, Right, Bottom, ScreenDC, FImagePosition.X + Left, FImagePosition.Y + Top, - SRCCOPY); - end; - end; - end; - InternalShowDragImage(ScreenDC); - finally - ReleaseDC(0, ScreenDC); - end; - end; - FLastPosition.X := P.X; - FLastPosition.Y := P.Y; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTDragImage.EndDrag; - -begin - HideDragImage; - FStates := FStates - [disInDrag, disPrepared]; - - FBackImage.Free; - FBackImage := nil; - FDragImage.Free; - FDragImage := nil; - FAlphaImage.Free; - FAlphaImage := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragImage.GetDragImageRect: TRect; - -// Returns the current size and position of the drag image (screen coordinates). - -begin - if Visible then - begin - with FBackImage do - Result := Rect(FImagePosition.X, FImagePosition.Y, FImagePosition.X + Width, FImagePosition.Y + Height); - end - else - Result := Rect(0, 0, 0, 0); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTDragImage.HideDragImage; - -var - ScreenDC: HDC; - -begin - if Visible then - begin - Include(FStates, disHidden); - ScreenDC := GetDC(0); - try - // restore screen - with FBackImage do - BitBlt(ScreenDC, FImagePosition.X, FImagePosition.Y, Width, Height, Canvas.Handle, 0, 0, SRCCOPY); - finally - ReleaseDC(0, ScreenDC); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTDragImage.PrepareDrag(DragImage: TBitmap; ImagePosition, HotSpot: TPoint; const DataObject: IDataObject); - -// Creates all necessary structures to do alpha blended dragging using the given image. -// ImagePostion and HotSpot are given in screen coordinates. The first determines where to place the drag image while -// the second is the initial mouse position. -// This method also determines whether the system supports drag images natively. If so then only minimal structures -// are created. - -var - Width, - Height: Integer; - DragSourceHelper: IDragSourceHelper; - DragInfo: TSHDragImage; - lDragSourceHelper2: IDragSourceHelper2;// Needed to get Windows Vista+ style drag hints. - lNullPoint: TPoint; -begin - Width := DragImage.Width; - Height := DragImage.Height; - - // Determine whether the system supports the drag helper interfaces. - if Assigned(DataObject) and Succeeded(CoCreateInstance(CLSID_DragDropHelper, nil, CLSCTX_INPROC_SERVER, - IDragSourceHelper, DragSourceHelper)) then - begin - Include(FStates, disSystemSupport); - lNullPoint := Point(0,0); - if Supports(DragSourceHelper, IDragSourceHelper2, lDragSourceHelper2) then - lDragSourceHelper2.SetFlags(DSH_ALLOWDROPDESCRIPTIONTEXT);// Show description texts - // First let the system try to initialze the DragSourceHelper, this works fine for file system objects (CF_HDROP) - StandardOLEFormat.cfFormat := CF_HDROP; - if not Succeeded(DataObject.QueryGetData(StandardOLEFormat)) or not Succeeded(DragSourceHelper.InitializeFromWindow(0, lNullPoint, DataObject)) then - begin - // Supply the drag source helper with our drag image. - DragInfo.sizeDragImage.cx := Width; - DragInfo.sizeDragImage.cy := Height; - DragInfo.ptOffset.x := Width div 2; - DragInfo.ptOffset.y := Height div 2; - DragInfo.hbmpDragImage := CopyImage(DragImage.Handle, IMAGE_BITMAP, Width, Height, LR_COPYRETURNORG); - DragInfo.crColorKey := ColorToRGB(FColorKey); - if not Succeeded(DragSourceHelper.InitializeFromBitmap(@DragInfo, DataObject)) then - begin - DeleteObject(DragInfo.hbmpDragImage); - Exclude(FStates, disSystemSupport); - end; - end; - end - else - Exclude(FStates, disSystemSupport); - - if not (disSystemSupport in FStates) then - begin - FLastPosition := HotSpot; - - FDragImage := TBitmap.Create; - FDragImage.PixelFormat := pf32Bit; - FDragImage.SetSize(Width, Height); - - FAlphaImage := TBitmap.Create; - FAlphaImage.PixelFormat := pf32Bit; - FAlphaImage.SetSize(Width, Height); - - FBackImage := TBitmap.Create; - FBackImage.PixelFormat := pf32Bit; - FBackImage.SetSize(Width, Height); - - // Copy the given drag image and apply pre blend bias if required. - if FPreBlendBias = 0 then - with FDragImage do - BitBlt(Canvas.Handle, 0, 0, Width, Height, DragImage.Canvas.Handle, 0, 0, SRCCOPY) - else - AlphaBlend(DragImage.Canvas.Handle, FDragImage.Canvas.Handle, Rect(0, 0, Width, Height), Point(0, 0), - bmConstantAlpha, 255, FPreBlendBias); - - // Create a proper alpha channel also if no fading is required (transparent parts). - MakeAlphaChannel(DragImage, FDragImage); - - FImagePosition := ImagePosition; - - // Initially the drag image is hidden and will be shown during the immediately following DragEnter event. - FStates := FStates + [disInDrag, disHidden, disPrepared]; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTDragImage.RecaptureBackground(Tree: TBaseVirtualTree; R: TRect; VisibleRegion: HRGN; - CaptureNCArea, ReshowDragImage: Boolean); - -// Notification by the drop target tree to update the background image because something in the tree has changed. -// Note: The passed rectangle is given in client coordinates of the current drop target tree (given in Tree). -// The caller does not check if the given rectangle is actually within the drag image. Hence this method must do -// all the checks. -// This method does nothing if the system manages the drag image. - -var - DragRect, - ClipRect: TRect; - PaintTarget: TPoint; - PaintOptions: TVTInternalPaintOptions; - ScreenDC: HDC; - -begin - // Recapturing means we want the tree to paint the new part into our back bitmap instead to the screen. - if Visible then - begin - // Create the minimum rectangle to be recaptured. - MapWindowPoints(Tree.Handle, 0, R, 2); - DragRect := GetDragImageRect; - IntersectRect(R, R, DragRect); - - OffsetRgn(VisibleRegion, -DragRect.Left, -DragRect.Top); - - // The target position for painting in the drag image is relative and can be determined from screen coordinates too. - PaintTarget.X := R.Left - DragRect.Left; - PaintTarget.Y := R.Top - DragRect.Top; - - // The source rectangle is determined by the offsets in the tree. - MapWindowPoints(0, Tree.Handle, R, 2); - OffsetRect(R, -Tree.FOffsetX, -Tree.FOffsetY); - - // Finally let the tree paint the relevant part and upate the drag image on screen. - PaintOptions := [poBackground, poColumnColor, poDrawFocusRect, poDrawDropMark, poDrawSelection, poGridLines]; - with FBackImage do - begin - ClipRect.TopLeft := PaintTarget; - ClipRect.Right := ClipRect.Left + R.Right - R.Left; - ClipRect.Bottom := ClipRect.Top + R.Bottom - R.Top; - // TODO: somehow with clipping, the background image is not drawn on the - // backup image. Need to be diagnosed and fixed. For now, we have coded - // a work around in DragTo where this is used by using the condition - // IsInHeader. (found when solving issue 248) - ClipCanvas(Canvas, ClipRect, VisibleRegion); - Tree.PaintTree(Canvas, R, PaintTarget, PaintOptions); - - if CaptureNCArea then - begin - // Header is painted in this part only so when you use this routine and want - // to capture the header in backup image, this flag should be ON. - // For the non-client area we only need the visible region of the window as limit for painting. - SelectClipRgn(Canvas.Handle, VisibleRegion); - // Since WM_PRINT cannot be given a position where to draw we simply move the window origin and - // get the same effect. - GetWindowRect(Tree.Handle, ClipRect); - SetCanvasOrigin(Canvas, DragRect.Left - ClipRect.Left, DragRect.Top - ClipRect.Top); - Tree.Perform(WM_PRINT, WPARAM(Canvas.Handle), PRF_NONCLIENT); - SetCanvasOrigin(Canvas, 0, 0); - end; - SelectClipRgn(Canvas.Handle, 0); - - if ReshowDragImage then - begin - GDIFlush; - ScreenDC := GetDC(0); - try - InternalShowDragImage(ScreenDC); - finally - ReleaseDC(0, ScreenDC); - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTDragImage.ShowDragImage; - -// Shows the drag image after it has been hidden by HideDragImage. -// Note: there might be a new background now. -// Also this method does nothing if the system manages the drag image. - -var - ScreenDC: HDC; - -begin - if FStates * [disInDrag, disHidden, disPrepared, disSystemSupport] = [disInDrag, disHidden, disPrepared] then - begin - Exclude(FStates, disHidden); - - GDIFlush; - ScreenDC := GetDC(0); - try - BitBlt(FBackImage.Canvas.Handle, 0, 0, FBackImage.Width, FBackImage.Height, ScreenDC, FImagePosition.X, - FImagePosition.Y, SRCCOPY); - - InternalShowDragImage(ScreenDC); - finally - ReleaseDC(0, ScreenDC); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTDragImage.WillMove(P: TPoint): Boolean; - -// This method determines whether the drag image would "physically" move when DragTo would be called with the same -// target point. -// Always returns False if the system drag image support is available. - -begin - Result := Visible; - if Result then - begin - // Determine distances to move the drag image. Take care for restrictions. - case FRestriction of - dmrHorizontalOnly: - Result := FLastPosition.X <> P.X; - dmrVerticalOnly: - Result := FLastPosition.Y <> P.Y; - else // dmrNone - Result := (FLastPosition.X <> P.X) or (FLastPosition.Y <> P.Y); - end; - end; -end; - -//----------------- TVTVirtualNodeEnumerator --------------------------------------------------------------------------- - -function TVTVirtualNodeEnumerator.GetCurrent: PVirtualNode; - -begin - Result := FNode; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTVirtualNodeEnumerator.MoveNext: Boolean; - -begin - Result := FCanMoveNext; - if Result then - begin - FNode := FEnumeration.GetNext(FNode); - Result := FNode <> nil; - FCanMoveNext := Result; - end; -end; - -//----------------- TVTVirtualNodeEnumeration -------------------------------------------------------------------------- - -function TVTVirtualNodeEnumeration.GetEnumerator: TVTVirtualNodeEnumerator; - -begin - Result.FNode := nil; - Result.FCanMoveNext := True; - Result.FEnumeration := @Self; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTVirtualNodeEnumeration.GetNext(Node: PVirtualNode): PVirtualNode; -begin - case FMode of - vneAll: - if Node = nil then - Result := FTree.GetFirst(FConsiderChildrenAbove) - else - Result := FTree.GetNext(Node, FConsiderChildrenAbove); - - vneChecked: - if Node = nil then - Result := FTree.GetFirstChecked(FState, FConsiderChildrenAbove) - else - Result := FTree.GetNextChecked(Node, FState, FConsiderChildrenAbove); - - vneChild: - if Node = nil then - Result := FTree.GetFirstChild(FNode) - else - Result := FTree.GetNextSibling(Node); - - vneCutCopy: - if Node = nil then - Result := FTree.GetFirstCutCopy(FConsiderChildrenAbove) - else - Result := FTree.GetNextCutCopy(Node, FConsiderChildrenAbove); - - vneInitialized: - if Node = nil then - Result := FTree.GetFirstInitialized(FConsiderChildrenAbove) - else - Result := FTree.GetNextInitialized(Node, FConsiderChildrenAbove); - - vneLeaf: - if Node = nil then - Result := FTree.GetFirstLeaf - else - Result := FTree.GetNextLeaf(Node); - - vneLevel: - if Node = nil then - Result := FTree.GetFirstLevel(FNodeLevel) - else - Result := FTree.GetNextLevel(Node, FNodeLevel); - - vneNoInit: - if Node = nil then - Result := FTree.GetFirstNoInit(FConsiderChildrenAbove) - else - Result := FTree.GetNextNoInit(Node, FConsiderChildrenAbove); - - vneSelected: - if Node = nil then - Result := FTree.GetFirstSelected(FConsiderChildrenAbove) - else - Result := FTree.GetNextSelected(Node, FConsiderChildrenAbove); - - vneVisible: - begin - if Node = nil then - begin - Result := FTree.GetFirstVisible(FNode, FConsiderChildrenAbove, FIncludeFiltered); - if FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result] then - Exit; - end; - repeat - Result := FTree.GetNextVisible(Node{, FConsiderChildrenAbove}); - until not Assigned(Result) or FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result]; - end; - - vneVisibleChild: - if Node = nil then - Result := FTree.GetFirstVisibleChild(FNode, FIncludeFiltered) - else - Result := FTree.GetNextVisibleSibling(Node, FIncludeFiltered); - - vneVisibleNoInitChild: - if Node = nil then - Result := FTree.GetFirstVisibleChildNoInit(FNode, FIncludeFiltered) - else - Result := FTree.GetNextVisibleSiblingNoInit(Node, FIncludeFiltered); - - vneVisibleNoInit: - begin - if Node = nil then - begin - Result := FTree.GetFirstVisibleNoInit(FNode, FConsiderChildrenAbove, FIncludeFiltered); - if FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result] then - Exit; - end; - repeat - Result := FTree.GetNextVisibleNoInit(Node, FConsiderChildrenAbove); - until not Assigned(Result) or FIncludeFiltered or not FTree.IsEffectivelyFiltered[Result]; - end; - else - Result := nil; - end; -end; - -//----------------- TVirtualTreeColumn --------------------------------------------------------------------------------- - -constructor TVirtualTreeColumn.Create(Collection: TCollection); - -begin - FMinWidth := 10; - FMaxWidth := 10000; - FImageIndex := -1; - FMargin := 4; - FSpacing := cDefaultColumnSpacing; - FText := ''; - FOptions := DefaultColumnOptions; - FAlignment := taLeftJustify; - FBiDiMode := bdLeftToRight; - FColor := clWindow; - FLayout := blGlyphLeft; - FBonusPixel := False; - FCaptionAlignment := taLeftJustify; - FCheckType := ctCheckBox; - FCheckState := csUncheckedNormal; - FCheckBox := False; - FHasImage := False; - FDefaultSortDirection := sdAscending; - fEditNextColumn := -1; - - inherited Create(Collection); - - if Assigned(Owner) then begin - FWidth := Owner.FDefaultWidth; - FLastWidth := Owner.FDefaultWidth; - FPosition := Owner.Count - 1; - end; -end; - -procedure TVirtualTreeColumn.SetCollection(Value: TCollection); -begin - inherited; - // Read parent bidi mode and color values as default values. - ParentBiDiModeChanged; - ParentColorChanged; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -destructor TVirtualTreeColumn.Destroy; - -var - I: Integer; - - //--------------- local function --------------------------------------------- - - procedure AdjustColumnIndex(var ColumnIndex: TColumnIndex); - - begin - if Index = ColumnIndex then - ColumnIndex := NoColumn - else - if Index < ColumnIndex then - Dec(ColumnIndex); - end; - - //--------------- end local function ----------------------------------------- - -begin - // Check if this column is somehow referenced by its collection parent or the header. - with Owner do - begin - // If the columns collection object is currently deleting all columns - // then we don't need to check the various cached indices individually. - if not FClearing then - begin - Header.Treeview.CancelEditNode; - IndexChanged(Index, -1); - - AdjustColumnIndex(FHoverIndex); - AdjustColumnIndex(FDownIndex); - AdjustColumnIndex(FTrackIndex); - AdjustColumnIndex(FClickIndex); - - with Header do - begin - AdjustColumnIndex(FAutoSizeIndex); - if Index = FMainColumn then - begin - // If the current main column is about to be destroyed then we have to find a new main column. - FMainColumn := NoColumn; - for I := 0 to Count - 1 do - if I <> Index then - begin - FMainColumn := I; - Break; - end; - end; - AdjustColumnIndex(FSortColumn); - end; - end; - end; - - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.GetCaptionAlignment: TAlignment; - -begin - if coUseCaptionAlignment in FOptions then - Result := FCaptionAlignment - else - Result := FAlignment; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.GetCaptionWidth: TDimension; -var - Theme: HTHEME; - AdvancedOwnerDraw: Boolean; - PaintInfo: THeaderPaintInfo; - RequestedElements: THeaderPaintElements; - - TextSize: TSize; - HeaderGlyphSize: TPoint; - UseText: Boolean; - R: TRect; -begin - AdvancedOwnerDraw := (hoOwnerDraw in Owner.Header.Options) and Assigned(Owner.Header.Treeview.FOnAdvancedHeaderDraw) and Assigned(Owner.Header.Treeview.FOnHeaderDrawQueryElements) and - not(csDesigning in Owner.Header.Treeview.ComponentState); - - PaintInfo.Column := Self; - PaintInfo.TargetCanvas := Owner.FHeaderBitmap.Canvas; - - with PaintInfo, Column do - begin - ShowHeaderGlyph := (hoShowImages in Owner.Header.Options) and ((Assigned(Owner.Header.Images) and (FImageIndex > -1)) or FCheckBox); - ShowSortGlyph := ((Owner.Header.FSortColumn > -1) and (Self = Owner.Items[Owner.Header.SortColumn])) and (hoShowSortGlyphs in Owner.Header.Options); - - // This path for text columns or advanced owner draw. - // See if the application wants to draw part of the header itself. - RequestedElements := []; - if AdvancedOwnerDraw then - begin - PaintInfo.Column := Self; - Owner.Header.Treeview.DoHeaderDrawQueryElements(PaintInfo, RequestedElements); - end; - end; - - UseText := Length(FText) > 0; - // If nothing is to show then don't waste time with useless preparation. - if not(UseText or PaintInfo.ShowHeaderGlyph or PaintInfo.ShowSortGlyph) then - Exit(0); - - // Calculate sizes of the involved items. - with Owner, Header do - begin - if PaintInfo.ShowHeaderGlyph then - if not FCheckBox then - begin - if Assigned(FImages) then - HeaderGlyphSize := Point(FImages.Width, FImages.Height); - end - else - with Self.Owner.Header.Treeview do - begin - if Assigned(FCheckImages) then - HeaderGlyphSize := Point(FCheckImages.Width, FCheckImages.Height); - end - else - HeaderGlyphSize := Point(0, 0); - if PaintInfo.ShowSortGlyph then - begin - if tsUseExplorerTheme in FHeader.Treeview.FStates then - begin - R := Rect(0, 0, 100, 100); - Theme := OpenThemeData(FHeader.Treeview.Handle, 'HEADER'); - GetThemePartSize(Theme, PaintInfo.TargetCanvas.Handle, HP_HEADERSORTARROW, HSAS_SORTEDUP, @R, TS_TRUE, PaintInfo.SortGlyphSize); - CloseThemeData(Theme); - end - else - begin - PaintInfo.SortGlyphSize.cx := Header.Treeview.ScaledPixels(16); - PaintInfo.SortGlyphSize.cy := Header.Treeview.ScaledPixels(4); - end; - end - else - begin - PaintInfo.SortGlyphSize.cx := 0; - PaintInfo.SortGlyphSize.cy := 0; - end; - end; - - if UseText then - begin - GetTextExtentPoint32W(PaintInfo.TargetCanvas.Handle, PWideChar(FText), Length(FText), TextSize); - Inc(TextSize.cx, 2); - end - else - begin - TextSize.cx := 0; - TextSize.cy := 0; - end; - - // if CalculateTextRect then - Result := TextSize.cx; - if PaintInfo.ShowHeaderGlyph then - if Layout in [blGlyphLeft, blGlyphRight] then - Inc(Result, HeaderGlyphSize.X + FSpacing) - else // if Layout in [ blGlyphTop, blGlyphBottom] then - Result := Max(Result, HeaderGlyphSize.X); - if PaintInfo.ShowSortGlyph then - Inc(Result, PaintInfo.SortGlyphSize.cx + FSpacing + 2); // without this +2, there is a slight movement of the sort glyph when expanding the column - -end; -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.GetLeft: Integer; - -begin - Result := FLeft; - if [coVisible, coFixed] * FOptions <> [coVisible, coFixed] then - Dec(Result, Owner.Header.Treeview.FEffectiveOffsetX); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.IsBiDiModeStored: Boolean; - -begin - Result := not (coParentBiDiMode in FOptions); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.IsCaptionAlignmentStored: Boolean; - -begin - Result := coUseCaptionAlignment in FOptions; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.IsColorStored: Boolean; - -begin - Result := not (coParentColor in FOptions); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetAlignment(const Value: TAlignment); - -begin - if FAlignment <> Value then - begin - FAlignment := Value; - Changed(False); - // Setting the alignment affects also the tree, hence invalidate it too. - Owner.Header.TreeView.Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetBiDiMode(Value: TBiDiMode); - -begin - if Value <> FBiDiMode then - begin - FBiDiMode := Value; - Exclude(FOptions, coParentBiDiMode); - Changed(False); - // Setting the alignment affects also the tree, hence invalidate it too. - Owner.Header.TreeView.Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetCaptionAlignment(const Value: TAlignment); - -begin - if not (coUseCaptionAlignment in FOptions) or (FCaptionAlignment <> Value) then - begin - FCaptionAlignment := Value; - Include(FOptions, coUseCaptionAlignment); - // Setting the alignment affects also the tree, hence invalidate it too. - Owner.Header.Invalidate(Self); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetColor(const Value: TColor); - -begin - if FColor <> Value then - begin - FColor := Value; - Exclude(FOptions, coParentColor); - Exclude(FOptions, coStyleColor); // Issue #919 - Changed(False); - Owner.Header.TreeView.Invalidate; - end; -end; - -function TVirtualTreeColumn.GetEffectiveColor(): TColor; -// Returns the color that should effectively be used as background color for this -// column considering all flags in the TVirtualTreeColumn.Options property -begin - if (coParentColor in Options) or ((coStyleColor in Options) and Owner.Header.TreeView.VclStyleEnabled) then - Result := Owner.Header.TreeView.FColors.BackGroundColor - else - Result := Self.Color; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetCheckBox(Value: Boolean); - -begin - if Value <> FCheckBox then - begin - FCheckBox := Value; - if Value and (csDesigning in Owner.Header.Treeview.ComponentState) then - Owner.Header.Options := Owner.Header.Options + [hoShowImages]; - Changed(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetCheckState(Value: TCheckState); - -begin - if Value <> FCheckState then - begin - FCheckState := Value; - Changed(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetCheckType(Value: TCheckType); - -begin - if Value <> FCheckType then - begin - FCheckType := Value; - Changed(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetImageIndex(Value: TImageIndex); - -begin - if Value <> FImageIndex then - begin - FImageIndex := Value; - Changed(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetLayout(Value: TVTHeaderColumnLayout); - -begin - if FLayout <> Value then - begin - FLayout := Value; - Changed(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetMargin(Value: Integer); - -begin - // Compatibility setting for -1. - if Value < 0 then - Value := 4; - if FMargin <> Value then - begin - FMargin := Value; - Changed(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetMaxWidth(Value: Integer); - -begin - if Value < FMinWidth then - Value := FMinWidth; - FMaxWidth := Value; - SetWidth(FWidth); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetMinWidth(Value: Integer); - -begin - if Value < 0 then - Value := 0; - if Value > FMaxWidth then - Value := FMaxWidth; - FMinWidth := Value; - SetWidth(FWidth); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetOptions(Value: TVTColumnOptions); - -var - ToBeSet, - ToBeCleared: TVTColumnOptions; - VisibleChanged, - lParentColorSet: Boolean; - lTreeView: TBaseVirtualTree; -begin - if FOptions <> Value then - begin - ToBeCleared := FOptions - Value; - ToBeSet := Value - FOptions; - - FOptions := Value; - - VisibleChanged := coVisible in (ToBeSet + ToBeCleared); - lParentColorSet := coParentColor in ToBeSet; - - if coParentBidiMode in ToBeSet then - ParentBiDiModeChanged; - if lParentColorSet then begin - Include(FOptions, coStyleColor);// Issue #919 - ParentColorChanged(); - end; - - if coAutoSpring in ToBeSet then - FSpringRest := 0; - - if coVisible in ToBeCleared then - Owner.Header.UpdateMainColumn(); // Fixes issue #946 - - if ((coFixed in ToBeSet) or (coFixed in ToBeCleared)) and (coVisible in FOptions) then - Owner.Header.RescaleHeader; - - Changed(False); - // Need to repaint and adjust the owner tree too. - lTreeView := Owner.Header.Treeview; - if not (csLoading in lTreeview.ComponentState) and (VisibleChanged or lParentColorSet) and (Owner.UpdateCount = 0) and lTreeView.HandleAllocated then - begin - lTreeview.Invalidate(); - if VisibleChanged then begin - lTreeview.DoColumnVisibilityChanged(Self.Index, coVisible in ToBeSet); - lTreeview.UpdateHorizontalScrollBar(False); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetPosition(Value: TColumnPosition); - -var - Temp: TColumnIndex; - -begin - if (csLoading in Owner.Header.Treeview.ComponentState) or (Owner.UpdateCount > 0) then - // Only cache the position for final fixup when loading from DFM. - FPosition := Value - else - begin - if Value >= TColumnPosition(Collection.Count) then - Value := Collection.Count - 1; - if FPosition <> Value then - begin - with Owner do - begin - InitializePositionArray; - Header.Treeview.CancelEditNode; - AdjustPosition(Self, Value); - Self.Changed(False); - - // Need to repaint. - with Header do - begin - if (UpdateCount = 0) and Treeview.HandleAllocated then - begin - Invalidate(Self); - Treeview.Invalidate; - end; - end; - end; - - // If the moved column is now within the fixed columns then we make it fixed as well. If it's not - // we clear the fixed state (in case that fixed column is moved outside fixed area). - if (coFixed in FOptions) and (FPosition > 0) then - Temp := Owner.ColumnFromPosition(FPosition - 1) - else - Temp := Owner.ColumnFromPosition(FPosition + 1); - - if Temp <> NoColumn then - begin - if coFixed in Owner[Temp].Options then - Options := Options + [coFixed] - else - Options := Options - [coFixed]; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetSpacing(Value: Integer); - -begin - if FSpacing <> Value then - begin - FSpacing := Value; - Changed(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetStyle(Value: TVirtualTreeColumnStyle); - -begin - if FStyle <> Value then - begin - FStyle := Value; - Changed(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetText(const Value: string); - -begin - if FText <> Value then - begin - FText := Value; - FCaptionText := ''; - Changed(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SetWidth(Value: Integer); - -var - EffectiveMaxWidth, - EffectiveMinWidth, - TotalFixedMaxWidth, - TotalFixedMinWidth: Integer; - I: TColumnIndex; - -begin - if not (hsScaling in Owner.Header.States) then - if ([coVisible, coFixed] * FOptions = [coVisible, coFixed]) then - begin - with Owner, FHeader, FFixedAreaConstraints, TreeView do - begin - TotalFixedMinWidth := 0; - TotalFixedMaxWidth := 0; - for I := 0 to FColumns.Count - 1 do - if ([coVisible, coFixed] * FColumns[I].Options = [coVisible, coFixed]) then - begin - Inc(TotalFixedMaxWidth, FColumns[I].MaxWidth); - Inc(TotalFixedMinWidth, FColumns[I].MinWidth); - end; - - if HandleAllocated then // Prevent premature creation of window handle, see issue #1073 - begin - // The percentage values have precedence over the pixel values. - If FMaxWidthPercent > 0 then - TotalFixedMinWidth:= Min((ClientWidth * FMaxWidthPercent) div 100, TotalFixedMinWidth); - If FMinWidthPercent > 0 then - TotalFixedMaxWidth := Max((ClientWidth * FMinWidthPercent) div 100, TotalFixedMaxWidth); - - EffectiveMaxWidth := Min(TotalFixedMaxWidth - (GetVisibleFixedWidth - Self.FWidth), FMaxWidth); - EffectiveMinWidth := Max(TotalFixedMinWidth - (GetVisibleFixedWidth - Self.FWidth), FMinWidth); - Value := Min(Max(Value, EffectiveMinWidth), EffectiveMaxWidth); - - if FMinWidthPercent > 0 then - Value := Max((ClientWidth * FMinWidthPercent) div 100 - GetVisibleFixedWidth + Self.FWidth, Value); - if FMaxWidthPercent > 0 then - Value := Min((ClientWidth * FMaxWidthPercent) div 100 - GetVisibleFixedWidth + Self.FWidth, Value); - end;// if HandleAllocated - end; - end - else - Value := Min(Max(Value, FMinWidth), FMaxWidth); - - if FWidth <> Value then - begin - FLastWidth := FWidth; - if not (hsResizing in Owner.Header.States) then - FBonusPixel := False; - if not (hoAutoResize in Owner.Header.Options) or (Index <> Owner.Header.AutoSizeIndex) then - begin - FWidth := Value; - Owner.UpdatePositions; - end; - if not (csLoading in Owner.Header.Treeview.ComponentState) and (Owner.UpdateCount = 0) then - begin - if hoAutoResize in Owner.Header.Options then - Owner.AdjustAutoSize(Index); - Owner.Header.Treeview.DoColumnResize(Index); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.ChangeScale(M, D: TDimension; isDpiChange: Boolean); -begin - FMinWidth := MulDiv(FMinWidth, M, D); - FMaxWidth := MulDiv(FMaxWidth, M, D); - FSpacing := MulDiv(FSpacing, M, D); - Self.Width := MulDiv(Self.Width, M, D); -end; - -procedure TVirtualTreeColumn.ComputeHeaderLayout(var PaintInfo: THeaderPaintInfo; DrawFormat: Cardinal; CalculateTextRect: Boolean = False); - -// The layout of a column header is determined by a lot of factors. This method takes them all into account and -// determines all necessary positions and bounds: -// - for the header text -// - the header glyph -// - the sort glyph - -var - TextSize: TSize; - TextPos, - ClientSize, - HeaderGlyphSize: TPoint; - CurrentAlignment: TAlignment; - MinLeft, - MaxRight, - TextSpacing: Integer; - UseText: Boolean; - R: TRect; - Theme: HTHEME; - -begin - UseText := Length(FText) > 0; - // If nothing is to show then don't waste time with useless preparation. - if not (UseText or PaintInfo.ShowHeaderGlyph or PaintInfo.ShowSortGlyph) then - Exit; - - CurrentAlignment := CaptionAlignment; - if FBiDiMode <> bdLeftToRight then - ChangeBiDiModeAlignment(CurrentAlignment); - - // Calculate sizes of the involved items. - ClientSize := Point(PaintInfo.PaintRectangle.Right - PaintInfo.PaintRectangle.Left, PaintInfo.PaintRectangle.Bottom - PaintInfo.PaintRectangle.Top); - with Owner, Header do - begin - if PaintInfo.ShowHeaderGlyph then - if not FCheckBox then - HeaderGlyphSize := Point(FImages.Width, FImages.Height) - else - with Self.Owner.Header.Treeview do - begin - if Assigned(FCheckImages) then - HeaderGlyphSize := Point(FCheckImages.Width, FCheckImages.Height); - end - else - HeaderGlyphSize := Point(0, 0); - if PaintInfo.ShowSortGlyph then - begin - if tsUseExplorerTheme in FHeader.Treeview.FStates then - begin - R := Rect(0, 0, 100, 100); - Theme := OpenThemeData(FHeader.Treeview.Handle, 'HEADER'); - GetThemePartSize(Theme, PaintInfo.TargetCanvas.Handle, HP_HEADERSORTARROW, HSAS_SORTEDUP, @R, TS_TRUE, PaintInfo.SortGlyphSize); - CloseThemeData(Theme); - end - else - begin - PaintInfo.SortGlyphSize.cx := Header.Treeview.ScaledPixels(16); - PaintInfo.SortGlyphSize.cy := Header.Treeview.ScaledPixels(4); - end; - - // In any case, the sort glyph is vertically centered. - PaintInfo.SortGlyphPos.Y := (ClientSize.Y - PaintInfo.SortGlyphSize.cy) div 2; - end - else - begin - PaintInfo.SortGlyphSize.cx := 0; - PaintInfo.SortGlyphSize.cy := 0; - end; - end; - - if UseText then - begin - if not (coWrapCaption in FOptions) then - begin - FCaptionText := FText; - GetTextExtentPoint32W(PaintInfo.TargetCanvas.Handle, PWideChar(FText), Length(FText), TextSize); - Inc(TextSize.cx, 2); - PaintInfo.TextRectangle := Rect(0, 0, TextSize.cx, TextSize.cy); - end - else - begin - R := PaintInfo.PaintRectangle; - if FCaptionText = '' then - FCaptionText := WrapString(PaintInfo.TargetCanvas.Handle, FText, R, DT_RTLREADING and DrawFormat <> 0, DrawFormat); - - GetStringDrawRect(PaintInfo.TargetCanvas.Handle, FCaptionText, R, DrawFormat); - TextSize.cx := PaintInfo.PaintRectangle.Right - PaintInfo.PaintRectangle.Left; - TextSize.cy := R.Bottom - R.Top; - PaintInfo.TextRectangle := Rect(0, 0, TextSize.cx, TextSize.cy); - end; - TextSpacing := FSpacing; - end - else - begin - TextSpacing := 0; - TextSize.cx := 0; - TextSize.cy := 0; - end; - - // Check first for the special case where nothing is shown except the sort glyph. - if PaintInfo.ShowSortGlyph and not (UseText or PaintInfo.ShowHeaderGlyph) then - begin - // Center the sort glyph in the available area if nothing else is there. - PaintInfo.SortGlyphPos := Point((ClientSize.X - PaintInfo.SortGlyphSize.cx) div 2, (ClientSize.Y - PaintInfo.SortGlyphSize.cy) div 2); - end - else - begin - // Determine extents of text and glyph and calculate positions which are clear from the layout. - if (Layout in [blGlyphLeft, blGlyphRight]) or not PaintInfo.ShowHeaderGlyph then - begin - PaintInfo.GlyphPos.Y := (ClientSize.Y - HeaderGlyphSize.Y) div 2; - // If the text is taller than the given height, perform no vertical centration as this - // would make the text even less readable. - //Using Max() fixes badly positioned text if Extra Large fonts have been activated in the Windows display options - TextPos.Y := Max(-5, (ClientSize.Y - TextSize.cy) div 2); - end - else - begin - if Layout = blGlyphTop then - begin - PaintInfo.GlyphPos.Y := (ClientSize.Y - HeaderGlyphSize.Y - TextSize.cy - TextSpacing) div 2; - TextPos.Y := PaintInfo.GlyphPos.Y + HeaderGlyphSize.Y + TextSpacing; - end - else - begin - TextPos.Y := (ClientSize.Y - HeaderGlyphSize.Y - TextSize.cy - TextSpacing) div 2; - PaintInfo.GlyphPos.Y := TextPos.Y + TextSize.cy + TextSpacing; - end; - end; - - // Each alignment needs special consideration. - case CurrentAlignment of - taLeftJustify: - begin - MinLeft := FMargin; - if PaintInfo.ShowSortGlyph and (FBiDiMode <> bdLeftToRight) then - begin - // In RTL context is the sort glyph placed on the left hand side. - PaintInfo.SortGlyphPos.X := MinLeft; - Inc(MinLeft, PaintInfo.SortGlyphSize.cx + FSpacing); - end; - if Layout in [blGlyphTop, blGlyphBottom] then - begin - // Header glyph is above or below text, so both must be considered when calculating - // the left positition of the sort glyph (if it is on the right hand side). - TextPos.X := MinLeft; - if PaintInfo.ShowHeaderGlyph then - begin - PaintInfo.GlyphPos.X := (ClientSize.X - HeaderGlyphSize.X) div 2; - if PaintInfo.GlyphPos.X < MinLeft then - PaintInfo.GlyphPos.X := MinLeft; - MinLeft := Max(TextPos.X + TextSize.cx + TextSpacing, PaintInfo.GlyphPos.X + HeaderGlyphSize.X + FSpacing); - end - else - MinLeft := TextPos.X + TextSize.cx + TextSpacing; - end - else - begin - // Everything is lined up. TextSpacing might be 0 if there is no text. - // This simplifies the calculation because no extra tests are necessary. - if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphLeft) then - begin - PaintInfo.GlyphPos.X := MinLeft; - Inc(MinLeft, HeaderGlyphSize.X + FSpacing); - end; - TextPos.X := MinLeft; - Inc(MinLeft, TextSize.cx + TextSpacing); - if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphRight) then - begin - PaintInfo.GlyphPos.X := MinLeft; - Inc(MinLeft, HeaderGlyphSize.X + FSpacing); - end; - end; - if PaintInfo.ShowSortGlyph and (FBiDiMode = bdLeftToRight) then - PaintInfo.SortGlyphPos.X := MinLeft; - end; - taCenter: - begin - if Layout in [blGlyphTop, blGlyphBottom] then - begin - PaintInfo.GlyphPos.X := (ClientSize.X - HeaderGlyphSize.X) div 2; - TextPos.X := (ClientSize.X - TextSize.cx) div 2; - if PaintInfo.ShowSortGlyph then - Dec(TextPos.X, PaintInfo.SortGlyphSize.cx div 2); - end - else - begin - MinLeft := (ClientSize.X - HeaderGlyphSize.X - TextSpacing - TextSize.cx) div 2; - if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphLeft) then - begin - PaintInfo.GlyphPos.X := MinLeft; - Inc(MinLeft, HeaderGlyphSize.X + TextSpacing); - end; - TextPos.X := MinLeft; - Inc(MinLeft, TextSize.cx + TextSpacing); - if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphRight) then - PaintInfo.GlyphPos.X := MinLeft; - end; - if PaintInfo.ShowHeaderGlyph then - begin - MinLeft := Min(PaintInfo.GlyphPos.X, TextPos.X); - MaxRight := Max(PaintInfo.GlyphPos.X + HeaderGlyphSize.X, TextPos.X + TextSize.cx); - end - else - begin - MinLeft := TextPos.X; - MaxRight := TextPos.X + TextSize.cx; - end; - // Place the sort glyph directly to the left or right of the larger item. - if PaintInfo.ShowSortGlyph then - if FBiDiMode = bdLeftToRight then - begin - // Sort glyph on the right hand side. - PaintInfo.SortGlyphPos.X := MaxRight + FSpacing; - end - else - begin - // Sort glyph on the left hand side. - PaintInfo.SortGlyphPos.X := MinLeft - FSpacing - PaintInfo.SortGlyphSize.cx; - end; - end; - else - // taRightJustify - MaxRight := ClientSize.X - FMargin; - if PaintInfo.ShowSortGlyph and (FBiDiMode = bdLeftToRight) then - begin - // In LTR context is the sort glyph placed on the right hand side. - Dec(MaxRight, PaintInfo.SortGlyphSize.cx); - PaintInfo.SortGlyphPos.X := MaxRight; - Dec(MaxRight, FSpacing); - end; - if Layout in [blGlyphTop, blGlyphBottom] then - begin - TextPos.X := MaxRight - TextSize.cx; - if PaintInfo.ShowHeaderGlyph then - begin - PaintInfo.GlyphPos.X := (ClientSize.X - HeaderGlyphSize.X) div 2; - if PaintInfo.GlyphPos.X + HeaderGlyphSize.X + FSpacing > MaxRight then - PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X - FSpacing; - MaxRight := Min(TextPos.X - TextSpacing, PaintInfo.GlyphPos.X - FSpacing); - end - else - MaxRight := TextPos.X - TextSpacing; - end - else - begin - // Everything is lined up. TextSpacing might be 0 if there is no text. - // This simplifies the calculation because no extra tests are necessary. - if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphRight) then - begin - PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X; - MaxRight := PaintInfo.GlyphPos.X - FSpacing; - end; - TextPos.X := MaxRight - TextSize.cx; - MaxRight := TextPos.X - TextSpacing; - if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphLeft) then - begin - PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X; - MaxRight := PaintInfo.GlyphPos.X - FSpacing; - end; - end; - if PaintInfo.ShowSortGlyph and (FBiDiMode <> bdLeftToRight) then - PaintInfo.SortGlyphPos.X := MaxRight - PaintInfo.SortGlyphSize.cx; - end; - end; - - // Once the position of each element is determined there remains only one but important step. - // The horizontal positions of every element must be adjusted so that it always fits into the - // given header area. This is accomplished by shorten the text appropriately. - - // These are the maximum bounds. Nothing goes beyond them. - MinLeft := FMargin; - MaxRight := ClientSize.X - FMargin; - if PaintInfo.ShowSortGlyph then - begin - if FBiDiMode = bdLeftToRight then - begin - // Sort glyph on the right hand side. - if PaintInfo.SortGlyphPos.X + PaintInfo.SortGlyphSize.cx > MaxRight then - PaintInfo.SortGlyphPos.X := MaxRight - PaintInfo.SortGlyphSize.cx; - MaxRight := PaintInfo.SortGlyphPos.X - FSpacing; - end; - - // Consider also the left side of the sort glyph regardless of the bidi mode. - if PaintInfo.SortGlyphPos.X < MinLeft then - PaintInfo.SortGlyphPos.X := MinLeft; - // Left border needs only adjustment if the sort glyph marks the left border. - if FBiDiMode <> bdLeftToRight then - MinLeft := PaintInfo.SortGlyphPos.X + PaintInfo.SortGlyphSize.cx + FSpacing; - - // Finally transform sort glyph to its actual position. - Inc(PaintInfo.SortGlyphPos.X, PaintInfo.PaintRectangle.Left); - Inc(PaintInfo.SortGlyphPos.Y, PaintInfo.PaintRectangle.Top); - end; - if PaintInfo.ShowHeaderGlyph then - begin - if PaintInfo.GlyphPos.X + HeaderGlyphSize.X > MaxRight then - PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X; - if Layout = blGlyphRight then - MaxRight := PaintInfo.GlyphPos.X - FSpacing; - if PaintInfo.GlyphPos.X < MinLeft then - PaintInfo.GlyphPos.X := MinLeft; - if Layout = blGlyphLeft then - MinLeft := PaintInfo.GlyphPos.X + HeaderGlyphSize.X + FSpacing; - if FCheckBox and (Owner.Header.MainColumn = Self.Index) then - Dec(PaintInfo.GlyphPos.X, 2) - else - if Owner.Header.MainColumn <> Self.Index then - Dec(PaintInfo.GlyphPos.X, 2); - - // Finally transform header glyph to its actual position. - Inc(PaintInfo.GlyphPos.X, PaintInfo.PaintRectangle.Left); - Inc(PaintInfo.GlyphPos.Y, PaintInfo.PaintRectangle.Top); - end; - if UseText then - begin - if TextPos.X < MinLeft then - TextPos.X := MinLeft; - OffsetRect(PaintInfo.TextRectangle, TextPos.X, TextPos.Y); - if PaintInfo.TextRectangle.Right > MaxRight then - PaintInfo.TextRectangle.Right := MaxRight; - OffsetRect(PaintInfo.TextRectangle, PaintInfo.PaintRectangle.Left, PaintInfo.PaintRectangle.Top); - - if coWrapCaption in FOptions then - begin - // Wrap the column caption if necessary. - R := PaintInfo.TextRectangle; - FCaptionText := WrapString(PaintInfo.TargetCanvas.Handle, FText, R, DT_RTLREADING and DrawFormat <> 0, DrawFormat); - GetStringDrawRect(PaintInfo.TargetCanvas.Handle, FCaptionText, R, DrawFormat); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.DefineProperties(Filer: TFiler); - -begin - inherited; - - // These properites are remains from non-Unicode Delphi versions, readers remain for backward compatibility. - Filer.DefineProperty('WideText', ReadText, nil, False); - Filer.DefineProperty('WideHint', ReadHint, nil, False); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.GetAbsoluteBounds(var Left, Right: Integer); - -// Returns the column's left and right bounds in header coordinates, that is, independant of the scrolling position. - -begin - Left := FLeft; - Right := FLeft + FWidth; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.GetDisplayName: string; -begin - Result := FText // Use column header caption as display name -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.GetOwner: TVirtualTreeColumns; - -begin - Result := Collection as TVirtualTreeColumns; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.InternalSetWidth(const value : TDimension); -begin - FWidth := value; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.ReadText(Reader: TReader); - -begin - case Reader.NextValue of - vaLString, vaString: - SetText(Reader.ReadString); - else - SetText(Reader.ReadString); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.ReadHint(Reader: TReader); - -begin - case Reader.NextValue of - vaLString, vaString: - FHint := Reader.ReadString; - else - FHint := Reader.ReadString; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.Assign(Source: TPersistent); - -var - OldOptions: TVTColumnOptions; - -begin - if Source is TVirtualTreeColumn then - begin - OldOptions := FOptions; - FOptions := []; - - BiDiMode := TVirtualTreeColumn(Source).BiDiMode; - ImageIndex := TVirtualTreeColumn(Source).ImageIndex; - Layout := TVirtualTreeColumn(Source).Layout; - Margin := TVirtualTreeColumn(Source).Margin; - MaxWidth := TVirtualTreeColumn(Source).MaxWidth; - MinWidth := TVirtualTreeColumn(Source).MinWidth; - Position := TVirtualTreeColumn(Source).Position; - Spacing := TVirtualTreeColumn(Source).Spacing; - Style := TVirtualTreeColumn(Source).Style; - Text := TVirtualTreeColumn(Source).Text; - Hint := TVirtualTreeColumn(Source).Hint; - Width := TVirtualTreeColumn(Source).Width; - Alignment := TVirtualTreeColumn(Source).Alignment; - CaptionAlignment := TVirtualTreeColumn(Source).CaptionAlignment; - Color := TVirtualTreeColumn(Source).Color; - Tag := TVirtualTreeColumn(Source).Tag; - EditOptions := TVirtualTreeColumn(Source).EditOptions; - EditNextColumn := TVirtualTreeColumn(Source).EditNextColumn; - - // Order is important. Assign options last. - FOptions := OldOptions; - Options := TVirtualTreeColumn(Source).Options; - - Changed(False); - end - else - inherited Assign(Source); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.Equals(OtherColumnObj: TObject): Boolean; -var - OtherColumn : TVirtualTreeColumn; -begin - if OtherColumnObj is TVirtualTreeColumn then - begin - OtherColumn := TVirtualTreeColumn (OtherColumnObj); - Result := (BiDiMode = OtherColumn.BiDiMode) and - (ImageIndex = OtherColumn.ImageIndex) and - (Layout = OtherColumn.Layout) and - (Margin = OtherColumn.Margin) and - (MaxWidth = OtherColumn.MaxWidth) and - (MinWidth = OtherColumn.MinWidth) and - (Position = OtherColumn.Position) and - (Spacing = OtherColumn.Spacing) and - (Style = OtherColumn.Style) and - (Text = OtherColumn.Text) and - (Hint = OtherColumn.Hint) and - (Width = OtherColumn.Width) and - (Alignment = OtherColumn.Alignment) and - (CaptionAlignment = OtherColumn.CaptionAlignment) and - (Color = OtherColumn.Color) and - (Tag = OtherColumn.Tag) and - (Options = OtherColumn.Options); - end - else - Result := False; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.GetRect: TRect; - -// Returns the rectangle this column occupies in the header (relative to (0, 0) of the non-client area). - -begin - with TVirtualTreeColumns(GetOwner).FHeader do - Result := Treeview.FHeaderRect; - Inc(Result.Left, FLeft); - Result.Right := Result.Left + FWidth; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -// [IPK] -function TVirtualTreeColumn.GetText: string; - -begin - Result := FText; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.LoadFromStream(const Stream: TStream; Version: Integer); -var - Dummy: Integer; - S: string; - -begin - with Stream do - begin - ReadBuffer(Dummy, SizeOf(Dummy)); - SetLength(S, Dummy); - ReadBuffer(PWideChar(S)^, 2 * Dummy); - Text := S; - ReadBuffer(Dummy, SizeOf(Dummy)); - SetLength(FHint, Dummy); - ReadBuffer(PWideChar(FHint)^, 2 * Dummy); - ReadBuffer(Dummy, SizeOf(Dummy)); - Width := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - MinWidth := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - MaxWidth := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - Style := TVirtualTreeColumnStyle(Dummy); - ReadBuffer(Dummy, SizeOf(Dummy)); - ImageIndex := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - Layout := TVTHeaderColumnLayout(Dummy); - ReadBuffer(Dummy, SizeOf(Dummy)); - Margin := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - Spacing := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - BiDiMode := TBiDiMode(Dummy); - - ReadBuffer(Dummy, SizeOf(Dummy)); - if Version >= 3 then - Options := TVTColumnOptions(Dummy); - - if Version > 0 then - begin - // Parts which have been introduced/changed with header stream version 1+. - ReadBuffer(Dummy, SizeOf(Dummy)); - Tag := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - Alignment := TAlignment(Dummy); - - if Version > 1 then - begin - ReadBuffer(Dummy, SizeOf(Dummy)); - Color := TColor(Dummy); - end; - - if Version > 5 then - begin - if coUseCaptionAlignment in FOptions then - begin - ReadBuffer(Dummy, SizeOf(Dummy)); - CaptionAlignment := TAlignment(Dummy); - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.ParentBiDiModeChanged; - -var - Columns: TVirtualTreeColumns; - -begin - if coParentBiDiMode in FOptions then - begin - Columns := GetOwner as TVirtualTreeColumns; - if Assigned(Columns) and (FBiDiMode <> Columns.FHeader.Treeview.BiDiMode) then - begin - FBiDiMode := Columns.FHeader.Treeview.BiDiMode; - Changed(False); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.ParentColorChanged; - -var - Columns: TVirtualTreeColumns; - -begin - if coParentColor in FOptions then - begin - Columns := GetOwner as TVirtualTreeColumns; - if Assigned(Columns) and (FColor <> Columns.FHeader.Treeview.Color) then - begin - FColor := Columns.FHeader.Treeview.Color; - Changed(False); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.RestoreLastWidth; - -begin - TVirtualTreeColumns(GetOwner).AnimatedResize(Index, FLastWidth); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumn.SaveToStream(const Stream: TStream); - -var - Dummy: Integer; - -begin - with Stream do - begin - Dummy := Length(FText); - WriteBuffer(Dummy, SizeOf(Dummy)); - WriteBuffer(PWideChar(FText)^, 2 * Dummy); - Dummy := Length(FHint); - WriteBuffer(Dummy, SizeOf(Dummy)); - WriteBuffer(PWideChar(FHint)^, 2 * Dummy); - WriteBuffer(FWidth, SizeOf(FWidth)); - WriteBuffer(FMinWidth, SizeOf(FMinWidth)); - WriteBuffer(FMaxWidth, SizeOf(FMaxWidth)); - Dummy := Ord(FStyle); - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := FImageIndex; - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Ord(FLayout); - WriteBuffer(Dummy, SizeOf(Dummy)); - WriteBuffer(FMargin, SizeOf(FMargin)); - WriteBuffer(FSpacing, SizeOf(FSpacing)); - Dummy := Ord(FBiDiMode); - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Integer(FOptions); - WriteBuffer(Dummy, SizeOf(Dummy)); - - // parts introduced with stream version 1 - WriteBuffer(FTag, SizeOf(Dummy)); - Dummy := Cardinal(FAlignment); - WriteBuffer(Dummy, SizeOf(Dummy)); - - // parts introduced with stream version 2 - Dummy := Integer(FColor); - WriteBuffer(Dummy, SizeOf(Dummy)); - - // parts introduced with stream version 6 - if coUseCaptionAlignment in FOptions then - begin - Dummy := Cardinal(FCaptionAlignment); - WriteBuffer(Dummy, SizeOf(Dummy)); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumn.UseRightToLeftReading: Boolean; - -begin - Result := FBiDiMode <> bdLeftToRight; -end; - -//----------------- TVirtualTreeColumns -------------------------------------------------------------------------------- - -constructor TVirtualTreeColumns.Create(AOwner: TVTHeader); - -var - ColumnClass: TVirtualTreeColumnClass; - -begin - FHeader := AOwner; - - // Determine column class to be used in the header. - ColumnClass := AOwner.FOwner.GetColumnClass; - // The owner tree always returns the default tree column class if not changed by application/descendants. - inherited Create(ColumnClass); - - FHeaderBitmap := TBitmap.Create; - FHeaderBitmap.PixelFormat := pf32Bit; - - FHoverIndex := NoColumn; - FDownIndex := NoColumn; - FClickIndex := NoColumn; - FDropTarget := NoColumn; - FTrackIndex := NoColumn; - FDefaultWidth := 50; - Self.FColumnPopupMenu := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -destructor TVirtualTreeColumns.Destroy; - -begin - FreeAndNil(FColumnPopupMenu); - FreeAndNil(FHeaderBitmap); - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetCount: Integer; - -begin - Result := inherited Count; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetItem(Index: TColumnIndex): TVirtualTreeColumn; - -begin - Result := TVirtualTreeColumn(inherited GetItem(Index)); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetNewIndex(P: TPoint; var OldIndex: TColumnIndex): Boolean; - -var - NewIndex: Integer; - -begin - Result := False; - // convert to local coordinates - Inc(P.Y, FHeader.Height); - NewIndex := ColumnFromPosition(P); - if NewIndex <> OldIndex then - begin - if OldIndex > NoColumn then - FHeader.Invalidate(Items[OldIndex], False, True); - OldIndex := NewIndex; - if OldIndex > NoColumn then - FHeader.Invalidate(Items[OldIndex], False, True); - Result := True; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.SetDefaultWidth(Value: Integer); - -begin - FDefaultWidth := Value; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.SetItem(Index: TColumnIndex; Value: TVirtualTreeColumn); - -begin - inherited SetItem(Index, Value); -end; - -function TVirtualTreeColumns.StyleServices(AControl: TControl): TCustomStyleServices; -begin - if AControl = nil then - AControl := FHeader.Treeview; - Result := VTStyleServices(AControl); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.AdjustAutoSize(CurrentIndex: TColumnIndex; Force: Boolean = False); - -// Called only if the header is in auto-size mode which means a column needs to be so large -// that it fills all the horizontal space not occupied by the other columns. -// CurrentIndex (if not InvalidColumn) describes which column has just been resized. - -var - NewValue, - AutoIndex, - Index, - RestWidth: Integer; - WasUpdating: Boolean; -begin - if Count > 0 then - begin - // Determine index to be used for auto resizing. This is usually given by the owner's AutoSizeIndex, but - // could be different if the column whose resize caused the invokation here is either the auto column itself - // or visually to the right of the auto size column. - AutoIndex := FHeader.AutoSizeIndex; - if (AutoIndex < 0) or (AutoIndex >= Count) then - AutoIndex := Count - 1; - - if AutoIndex >= 0 then - begin - with FHeader.Treeview do - begin - if HandleAllocated then - RestWidth := ClientWidth - else - RestWidth := Width; - end; - - // Go through all columns and calculate the rest space remaining. - for Index := 0 to Count - 1 do - if (Index <> AutoIndex) and (coVisible in Items[Index].Options) then - Dec(RestWidth, Items[Index].Width); - - with Items[AutoIndex] do - begin - NewValue := Max(MinWidth, Min(MaxWidth, RestWidth)); - if Force or (FWidth <> NewValue) then - begin - FWidth := NewValue; - UpdatePositions; - WasUpdating := csUpdating in FHeader.Treeview.ComponentState; - if not WasUpdating then - FHeader.Treeview.Updating();// Fixes #398 - try - FHeader.Treeview.DoColumnResize(AutoIndex); - finally - if not WasUpdating then - FHeader.Treeview.Updated(); - end; - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.AdjustDownColumn(P: TPoint): TColumnIndex; - -// Determines the column from the given position and returns it. If this column is allowed to be clicked then -// it is also kept for later use. - -begin - // Convert to local coordinates. - Inc(P.Y, FHeader.Height); - Result := ColumnFromPosition(P); - if (Result > NoColumn) and (Result <> FDownIndex) and (coAllowClick in Items[Result].Options) and - (coEnabled in Items[Result].Options) then - begin - if FDownIndex > NoColumn then - FHeader.Invalidate(Items[FDownIndex]); - FDownIndex := Result; - FCheckBoxHit := Items[Result].HasImage and PtInRect(Items[Result].ImageRect, P) and Items[Result].CheckBox; - FHeader.Invalidate(Items[FDownIndex]); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.AdjustHoverColumn(P: TPoint): Boolean; - -// Determines the new hover column index and returns True if the index actually changed else False. - -begin - Result := GetNewIndex(P, FHoverIndex); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.AdjustPosition(Column: TVirtualTreeColumn; Position: Cardinal); - -// Reorders the column position array so that the given column gets the given position. - -var - OldPosition: Cardinal; - -begin - OldPosition := Column.Position; - if OldPosition <> Position then - begin - if OldPosition < Position then - begin - // column will be moved up so move down other entries - Move(FPositionToIndex[OldPosition + 1], FPositionToIndex[OldPosition], (Position - OldPosition) * SizeOf(Cardinal)); - end - else - begin - // column will be moved down so move up other entries - Move(FPositionToIndex[Position], FPositionToIndex[Position + 1], (OldPosition - Position) * SizeOf(Cardinal)); - end; - FPositionToIndex[Position] := Column.Index; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.CanSplitterResize(P: TPoint; Column: TColumnIndex): Boolean; - -begin - Result := (Column > NoColumn) and ([coResizable, coVisible] * Items[Column].Options = [coResizable, coVisible]); - DoCanSplitterResize(P, Column, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.DoCanSplitterResize(P: TPoint; Column: TColumnIndex; var Allowed: Boolean); - -begin - if Assigned(FHeader.Treeview.FOnCanSplitterResizeColumn) then - FHeader.Treeview.FOnCanSplitterResizeColumn(FHeader, P, Column, Allowed); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.DrawButtonText(DC: HDC; Caption: string; Bounds: TRect; Enabled, Hot: Boolean; - DrawFormat: Cardinal; WrapCaption: Boolean); - -var - TextSpace: Integer; - Size: TSize; - -begin - if not WrapCaption then - begin - // Do we need to shorten the caption due to limited space? - GetTextExtentPoint32W(DC, PWideChar(Caption), Length(Caption), Size); - TextSpace := Bounds.Right - Bounds.Left; - if TextSpace < Size.cx then - Caption := ShortenString(DC, Caption, TextSpace); - end; - - SetBkMode(DC, TRANSPARENT); - if not Enabled then - if FHeader.Treeview.VclStyleEnabled then - begin - SetTextColor(DC, ColorToRGB(FHeader.Treeview.FColors.HeaderFontColor)); - Winapi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat); - end - else - begin - OffsetRect(Bounds, 1, 1); - SetTextColor(DC, ColorToRGB(clBtnHighlight)); - Winapi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat); - OffsetRect(Bounds, -1, -1); - SetTextColor(DC, ColorToRGB(clBtnShadow)); - Winapi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat); - end - else - begin - if Hot then - SetTextColor(DC, ColorToRGB(FHeader.Treeview.FColors.HeaderHotColor)) - else - SetTextColor(DC, ColorToRGB(FHeader.Treeview.FColors.HeaderFontColor)); - Winapi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.FixPositions; - -// Fixes column positions after loading from DFM or Bidi mode change. - -var - I: Integer; - -begin - // Fox positions that too large, see #1179 - for I := 0 to Count - 1 do - begin - if Integer(Items[I].Position) >= Count then - begin - UpdatePositions(True); - break; - end; - end; // for - - // Update position array - for I := 0 to Count - 1 do - FPositionToIndex[Items[I].Position] := I; - - FNeedPositionsFix := False; - UpdatePositions(True); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetColumnAndBounds(P: TPoint; var ColumnLeft, ColumnRight: Integer; - Relative: Boolean = True): Integer; - -// Returns the column where the mouse is currently in as well as the left and right bound of -// this column (Left and Right are undetermined if no column is involved). - -var - I: Integer; - -begin - Result := InvalidColumn; - if Relative and (P.X >= Header.Columns.GetVisibleFixedWidth) then - ColumnLeft := -FHeader.Treeview.FEffectiveOffsetX - else - ColumnLeft := 0; - - if FHeader.Treeview.UseRightToLeftAlignment then - Inc(ColumnLeft, FHeader.Treeview.ComputeRTLOffset(True)); - - for I := 0 to Count - 1 do - with Items[FPositionToIndex[I]] do - if coVisible in FOptions then - begin - ColumnRight := ColumnLeft + FWidth; - - //fix: in right to left alignment, X can be in the - //area on the left of first column which is OUT. - if (P.X < ColumnLeft) and (I = 0) then - begin - Result := InvalidColumn; - exit; - end; - if P.X < ColumnRight then - begin - Result := FPositionToIndex[I]; - Exit; - end; - ColumnLeft := ColumnRight; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetOwner: TPersistent; - -begin - Result := FHeader; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.HandleClick(P: TPoint; Button: TMouseButton; Force, DblClick: Boolean): Boolean; - -// Generates a click event if the mouse button has been released over the same column it was pressed first. -// Alternatively, Force might be set to True to indicate that the down index does not matter (right, middle and -// double click). -// Returns true if the click was handled, False otherwise. - -var - HitInfo: TVTHeaderHitInfo; - NewClickIndex: Integer; - Menu: TPopupMenu; -begin - Result := False; - if (csDesigning in Header.Treeview.ComponentState) then - exit; - // Convert vertical position to local coordinates. - Inc(P.Y, FHeader.Height); - NewClickIndex := ColumnFromPosition(P); - with HitInfo do - begin - X := P.X; - Y := P.Y; - Shift := FHeader.GetShiftState; - if DblClick then - Shift := Shift + [ssDouble]; - end; - HitInfo.Button := Button; - - if (NewClickIndex > NoColumn) and (coAllowClick in Items[NewClickIndex].Options) and - ((NewClickIndex = FDownIndex) or Force) then - begin - FClickIndex := NewClickIndex; - HitInfo.Column := NewClickIndex; - HitInfo.HitPosition := [hhiOnColumn]; - - if Items[NewClickIndex].HasImage and PtInRect(Items[NewClickIndex].ImageRect, P) then - begin - Include(HitInfo.HitPosition, hhiOnIcon); - if Items[NewClickIndex].CheckBox then - begin - if Button = mbLeft then - FHeader.Treeview.UpdateColumnCheckState(Items[NewClickIndex]); - Include(HitInfo.HitPosition, hhiOnCheckbox); - end; - end; - end - else - begin - FClickIndex := NoColumn; - HitInfo.Column := NoColumn; - HitInfo.HitPosition := [hhiNoWhere]; - end; - - if DblClick then - FHeader.Treeview.DoHeaderDblClick(HitInfo) - else begin - if (hoHeaderClickAutoSort in Header.Options) and (HitInfo.Button = mbLeft) and not (hhiOnCheckbox in HitInfo.HitPosition) and (HitInfo.Column >= 0) then - begin - // handle automatic setting of SortColumn and toggling of the sort order - if HitInfo.Column <> Header.SortColumn then - begin - // set sort column - Header.DoSetSortColumn(HitInfo.Column, Self[HitInfo.Column].DefaultSortDirection); - end//if - else - begin - // toggle sort direction - if Header.SortDirection = sdDescending then - Header.SortDirection := sdAscending - else - Header.SortDirection := sdDescending; - end;//else - Result := True; - end;//if - - if (Button = mbRight) then - begin - Dec(P.Y, FHeader.Height); // popup menus at actual clicked point - FreeAndNil(fColumnPopupMenu);// Attention: Do not free the TVTHeaderPopupMenu at the end of this method, otherwise the clikc events of the menu item will not be fired. - Self.FDownIndex := NoColumn; - Self.FTrackIndex := NoColumn; - Self.FCheckBoxHit := False; - Menu := Header.DoGetPopupMenu(Self.ColumnFromPosition(Point(P.X, P.Y + Integer(Header.Treeview.Height))), P); - if Assigned(Menu) then - begin - Header.Treeview.StopTimer(ScrollTimer); - Header.Treeview.StopTimer(HeaderTimer); - Header.Columns.SetHoverIndex(NoColumn); - Header.Treeview.DoStateChange([], [tsScrollPending, tsScrolling]); - - Menu.PopupComponent := Header.Treeview; - With Header.Treeview.ClientToScreen(P) do - Menu.Popup(X, Y); - Result := True; - end - else if (hoAutoColumnPopupMenu in Header.Options) then - begin - fColumnPopupMenu := TVTHeaderPopupMenu.Create(Header.TreeView); - TVTHeaderPopupMenu(fColumnPopupMenu).OnAddHeaderPopupItem := HeaderPopupMenuAddHeaderPopupItem; - TVTHeaderPopupMenu(fColumnPopupMenu).OnColumnChange := HeaderPopupMenuColumnChange; - fColumnPopupMenu.PopupComponent := Header.Treeview; - if (hoDblClickResize in Header.Options) and ((Header.Treeview.ChildCount[nil] > 0) or (hoAutoResizeInclCaption in Header.Options)) then - TVTHeaderPopupMenu(fColumnPopupMenu).Options := TVTHeaderPopupMenu(fColumnPopupMenu).Options + [poResizeToFitItem] - else - TVTHeaderPopupMenu(fColumnPopupMenu).Options := TVTHeaderPopupMenu(fColumnPopupMenu).Options - [poResizeToFitItem]; - With Header.Treeview.ClientToScreen(P) do - fColumnPopupMenu.Popup(X, Y); - Result := True; - end; // if hoAutoColumnPopupMenu - end;//if mbRight - FHeader.Treeview.DoHeaderClick(HitInfo); - end;//else (not DblClick) - - if not (hhiNoWhere in HitInfo.HitPosition) then - FHeader.Invalidate(Items[NewClickIndex]); - if (FClickIndex > NoColumn) and (FClickIndex <> NewClickIndex) then - FHeader.Invalidate(Items[FClickIndex]); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.HeaderPopupMenuAddHeaderPopupItem(const Sender: TBaseVirtualTree; const Column: TColumnIndex; - var Cmd: TAddPopupItemType); -begin - Sender.DoHeaderAddPopupItem(Column, Cmd); -end; - -//---------------------------------------------------------------------------------------------------------------------- - - -procedure TVirtualTreeColumns.HeaderPopupMenuColumnChange(const Sender: TBaseVirtualTree; const Column: TColumnIndex; Visible: Boolean); -begin - Sender.DoColumnVisibilityChanged(Column, Visible); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.IndexChanged(OldIndex, NewIndex: Integer); - -// Called by a column when its index in the collection changes. If NewIndex is -1 then the column is -// about to be removed, otherwise it is moved to a new index. -// The method will then update the position array to reflect the change. - -var - I: Integer; - Increment: Integer; - Lower, - Upper: Integer; - -begin - if NewIndex = -1 then - begin - // Find position in the array with the old index. - Upper := High(FPositionToIndex); - for I := 0 to Upper do - begin - if FPositionToIndex[I] = OldIndex then - begin - // Index found. Move all higher entries one step down and remove the last entry. - if I < Upper then - Move(FPositionToIndex[I + 1], FPositionToIndex[I], (Upper - I) * SizeOf(TColumnIndex)); - end; - // Decrease all indices, which are greater than the index to be deleted. - if FPositionToIndex[I] > OldIndex then - Dec(FPositionToIndex[I]); - end; - SetLength(FPositionToIndex, High(FPositionToIndex)); - end - else - begin - if OldIndex < NewIndex then - Increment := -1 - else - Increment := 1; - - Lower := Min(OldIndex, NewIndex); - Upper := Max(OldIndex, NewIndex); - for I := 0 to High(FPositionToIndex) do - begin - if (FPositionToIndex[I] >= Lower) and (FPositionToIndex[I] < Upper) then - Inc(FPositionToIndex[I], Increment) - else - if FPositionToIndex[I] = OldIndex then - FPositionToIndex[I] := NewIndex; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.InitializePositionArray; - -// Ensures that the column position array contains as many entries as columns are defined. -// The array is resized and initialized with default values if needed. - -var - I, OldSize: Integer; - Changed: Boolean; - -begin - if Count <> Length(FPositionToIndex) then - begin - OldSize := Length(FPositionToIndex); - SetLength(FPositionToIndex, Count); - if Count > OldSize then - begin - // New items have been added, just set their position to the same as their index. - for I := OldSize to Count - 1 do - FPositionToIndex[I] := I; - end - else - begin - // Items have been deleted, so reindex remaining entries by decrementing values larger than the highest - // possible index until no entry is higher than this limit. - repeat - Changed := False; - for I := 0 to Count - 1 do - if FPositionToIndex[I] >= Count then - begin - Dec(FPositionToIndex[I]); - Changed := True; - end; - until not Changed; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.Notify(Item: TCollectionItem; Action: System.Classes.TCollectionNotification); -var - I: Integer; -begin - if Action in [cnDeleting] then - begin - // Adjust all positions larger than the deleted column's position. Fixes #959, #1049 - for I := 0 to Count - 1 do begin - if Items[I].Position > TVirtualTreeColumn(Item).Position then - Items[I].Position := Items[I].Position - 1; - end;//for I - - with Header.Treeview do - if not (csLoading in ComponentState) and (FFocusedColumn = Item.Index) then - FFocusedColumn := NoColumn; - end;// if cnDeleting -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.ReorderColumns(RTL: Boolean); - -var - I: Integer; - -begin - if RTL then - begin - for I := 0 to Count - 1 do - FPositionToIndex[I] := Count - I - 1; - end - else - begin - for I := 0 to Count - 1 do - FPositionToIndex[I] := I; - end; - - UpdatePositions(True); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.SetHoverIndex(index : TColumnIndex); -begin - FHoverIndex := index; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.EndUpdate; -begin - InitializePositionArray(); - FixPositions(); // Accept the cuurent order. See issue #753 - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.Update(Item: TCollectionItem); - -begin - // This is the only place which gets notified when a new column has been added or removed - // and we need this event to adjust the column position array. - InitializePositionArray; - if csLoading in Header.Treeview.ComponentState then - FNeedPositionsFix := True - else - UpdatePositions; - - // The first column which is created is by definition also the main column. - if (Count > 0) and (Header.FMainColumn < 0) then - FHeader.MainColumn := 0; - - if not (csLoading in FHeader.Treeview.ComponentState) and not (hsLoading in FHeader.States) then - begin - with FHeader do - begin - if hoAutoResize in FOptions then - AdjustAutoSize(InvalidColumn); - if Assigned(Item) then - Invalidate(Item as TVirtualTreeColumn) - else - if Treeview.HandleAllocated then - begin - Treeview.UpdateHorizontalScrollBar(False); - Invalidate(nil); - Treeview.Invalidate; - end; - - if not (Treeview.IsUpdating) then - // This is mainly to let the designer know when a change occurs at design time which - // doesn't involve the object inspector (like column resizing with the mouse). - // This does NOT include design time code as the communication is done via an interface. - Treeview.UpdateDesigner; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.UpdatePositions(Force: Boolean = False); - -// Recalculates the left border of every column and updates their position property according to the -// PostionToIndex array which primarily determines where each column is placed visually. - -var - I, RunningPos: Integer; - -begin - if not (csDestroying in FHeader.Treeview.ComponentState) and not FNeedPositionsFix and (Force or (UpdateCount = 0)) then - begin - RunningPos := 0; - for I := 0 to High(FPositionToIndex) do - with Items[FPositionToIndex[I]] do - begin - FPosition := I; - FLeft := RunningPos; - if coVisible in FOptions then - Inc(RunningPos, FWidth); - end; - FHeader.Treeview.UpdateHorizontalScrollBar(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.Add: TVirtualTreeColumn; - -begin - Assert(GetCurrentThreadId = MainThreadId, 'UI controls may only be changed in UI thread.'); - Result := TVirtualTreeColumn(inherited Add); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.AnimatedResize(Column: TColumnIndex; NewWidth: Integer); - -// Resizes the given column animated by scrolling the window DC. - -var - OldWidth: Integer; - DC: HDC; - I, - Steps, - DX: Integer; - HeaderScrollRect, - ScrollRect, - R: TRect; - - NewBrush, - LastBrush: HBRUSH; - -begin - if not IsValidColumn(Column) then - Exit; // Just in case. - - // Make sure the width constrains are considered. - if NewWidth < Items[Column].MinWidth then - NewWidth := Items[Column].MinWidth; - if NewWidth > Items[Column].MaxWidth then - NewWidth := Items[Column].MaxWidth; - - OldWidth := Items[Column].Width; - // Nothing to do if the width is the same. - if OldWidth <> NewWidth then - begin - if not ( (hoDisableAnimatedResize in FHeader.Options) or - (coDisableAnimatedResize in Items[Column].Options) ) then - begin - DC := GetWindowDC(FHeader.Treeview.Handle); - with FHeader.Treeview do - try - Steps := 32; - DX := (NewWidth - OldWidth) div Steps; - - // Determination of the scroll rectangle is a bit complicated since we neither want - // to scroll the scrollbars nor the border of the treeview window. - HeaderScrollRect := FHeaderRect; - ScrollRect := HeaderScrollRect; - // Exclude the header itself from scrolling. - ScrollRect.Top := ScrollRect.Bottom; - ScrollRect.Bottom := ScrollRect.Top + ClientHeight; - ScrollRect.Right := ScrollRect.Left + ClientWidth; - with Items[Column] do - Inc(ScrollRect.Left, FLeft + FWidth); - HeaderScrollRect.Left := ScrollRect.Left; - HeaderScrollRect.Right := ScrollRect.Right; - - // When the new width is larger then avoid artefacts on the left hand side - // by deleting a small stripe - if NewWidth > OldWidth then - begin - R := ScrollRect; - NewBrush := CreateSolidBrush(ColorToRGB(Color)); - LastBrush := SelectObject(DC, NewBrush); - R.Right := R.Left + DX; - FillRect(DC, R, NewBrush); - SelectObject(DC, LastBrush); - DeleteObject(NewBrush); - end - else - begin - Inc(HeaderScrollRect.Left, DX); - Inc(ScrollRect.Left, DX); - end; - - for I := 0 to Steps - 1 do - begin - ScrollDC(DC, DX, 0, HeaderScrollRect, HeaderScrollRect, 0, nil); - Inc(HeaderScrollRect.Left, DX); - ScrollDC(DC, DX, 0, ScrollRect, ScrollRect, 0, nil); - Inc(ScrollRect.Left, DX); - Sleep(1); - end; - finally - ReleaseDC(Handle, DC); - end; - end; - Items[Column].Width := NewWidth; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.Assign(Source: TPersistent); - -begin - // Let the collection class assign the items. - inherited; - - if Source is TVirtualTreeColumns then - begin - // Copying the position array is the only needed task here. - FPositionToIndex := Copy(TVirtualTreeColumns(Source).FPositionToIndex, 0, MaxInt); - - // Make sure the left edges are correct after assignment. - FNeedPositionsFix := False; - UpdatePositions(True); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.Clear; - -begin - FClearing := True; - try - Header.Treeview.CancelEditNode; - - // Since we're freeing all columns, the following have to be true when we're done. - FHoverIndex := NoColumn; - FDownIndex := NoColumn; - FTrackIndex := NoColumn; - FClickIndex := NoColumn; - FCheckBoxHit := False; - - with Header do - if not (hsLoading in FStates) then - begin - FAutoSizeIndex := NoColumn; - FMainColumn := NoColumn; - FSortColumn := NoColumn; - end; - - with Header.Treeview do - if not (csLoading in ComponentState) then - FFocusedColumn := NoColumn; - - inherited Clear; - finally - FClearing := False; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.ColumnFromPosition(P: TPoint; Relative: Boolean = True): TColumnIndex; - -// Determines the current column based on the position passed in P. - -var - I, Sum: Integer; - -begin - Result := InvalidColumn; - - // The position must be within the header area, but we extend the vertical bounds to the entire treeview area. - if (P.X >= 0) and (P.Y >= 0) and (P.Y <= FHeader.TreeView.Height) then - with FHeader, Treeview do - begin - if Relative and (P.X >= GetVisibleFixedWidth) then - Sum := -FEffectiveOffsetX - else - Sum := 0; - - if UseRightToLeftAlignment then - Inc(Sum, ComputeRTLOffset(True)); - - for I := 0 to Count - 1 do - if coVisible in Items[FPositionToIndex[I]].Options then - begin - Inc(Sum, Items[FPositionToIndex[I]].Width); - if P.X < Sum then - begin - Result := FPositionToIndex[I]; - Break; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.ColumnFromPosition(PositionIndex: TColumnPosition): TColumnIndex; - -// Returns the index of the column at the given position. - -begin - if Integer(PositionIndex) < Length(FPositionToIndex) then - Result := FPositionToIndex[PositionIndex] - else - Result := NoColumn; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.Equals(OtherColumnsObj: TObject): Boolean; - -// Compares itself with the given set of columns and returns True if all published properties are the same -// (including column order), otherwise False is returned. - -var - I: Integer; - OtherColumns : TVirtualTreeColumns; - -begin - if not (OtherColumnsObj is TVirtualTreeColumns) then - begin - Result := False; - Exit; - end; - - OtherColumns := TVirtualTreeColumns (OtherColumnsObj); - - // Same number of columns? - Result := OtherColumns.Count = Count; - if Result then - begin - // Same order of columns? - Result := CompareMem(Pointer(FPositionToIndex), Pointer(OtherColumns.FPositionToIndex), - Length(FPositionToIndex) * SizeOf(TColumnIndex)); - if Result then - begin - for I := 0 to Count - 1 do - if not Items[I].Equals(OtherColumns[I]) then - begin - Result := False; - Break; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.GetColumnBounds(Column: TColumnIndex; var Left, Right: Integer); - -// Returns the left and right bound of the given column. If Column is NoColumn then the entire client width is returned. - -begin - if Column <= NoColumn then - begin - Left := 0; - Right := FHeader.Treeview.ClientWidth; - end - else - begin - Left := Items[Column].Left; - Right := Left + Items[Column].Width; - if FHeader.Treeview.UseRightToLeftAlignment then - begin - Inc(Left, FHeader.Treeview.ComputeRTLOffset(True)); - Inc(Right, FHeader.Treeview.ComputeRTLOffset(True)); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetScrollWidth: Integer; - -// Returns the average width of all visible, non-fixed columns. If there is no such column the indent is returned. - -var - I: Integer; - ScrollColumnCount: Integer; - -begin - - Result := 0; - - ScrollColumnCount := 0; - for I := 0 to FHeader.Columns.Count - 1 do - begin - if ([coVisible, coFixed] * FHeader.Columns[I].Options = [coVisible]) then - begin - Inc(Result, FHeader.Columns[I].Width); - Inc(ScrollColumnCount); - end; - end; - - if ScrollColumnCount > 0 then // use average width - Result := Round(Result / ScrollColumnCount) - else // use indent - Result := Integer(FHeader.Treeview.FIndent); - -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetFirstVisibleColumn(ConsiderAllowFocus: Boolean = False): TColumnIndex; - -// Returns the index of the first visible column or "InvalidColumn" if either no columns are defined or -// all columns are hidden. -// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed. - -var - I: Integer; - -begin - Result := InvalidColumn; - if (UpdateCount > 0) or (csLoading in Header.TreeView.ComponentState) then - exit; // See issue #760 - for I := 0 to Count - 1 do - if (coVisible in Items[FPositionToIndex[I]].Options) and - ( (not ConsiderAllowFocus) or - (coAllowFocus in Items[FPositionToIndex[I]].Options) - ) then - begin - Result := FPositionToIndex[I]; - Break; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetLastVisibleColumn(ConsiderAllowFocus: Boolean = False): TColumnIndex; - -// Returns the index of the last visible column or "InvalidColumn" if either no columns are defined or -// all columns are hidden. -// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed. - -var - I: Integer; - -begin - Result := InvalidColumn; - if (UpdateCount > 0) or (csLoading in Header.TreeView.ComponentState) then - exit; // See issue #760 - for I := Count - 1 downto 0 do - if (coVisible in Items[FPositionToIndex[I]].Options) and - ( (not ConsiderAllowFocus) or - (coAllowFocus in Items[FPositionToIndex[I]].Options) - ) then - begin - Result := FPositionToIndex[I]; - Break; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetFirstColumn: TColumnIndex; - -// Returns the first column in display order. - -begin - if Count = 0 then - Result := InvalidColumn - else - Result := FPositionToIndex[0]; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetNextColumn(Column: TColumnIndex): TColumnIndex; - -// Returns the next column in display order. Column is the index of an item in the collection (a column). - -var - Position: Integer; - -begin - if Column < 0 then - Result := InvalidColumn - else - begin - Position := Items[Column].Position; - if Position < Count - 1 then - Result := FPositionToIndex[Position + 1] - else - Result := InvalidColumn; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetNextVisibleColumn(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex; - -// Returns the next visible column in display order, Column is an index into the columns list. -// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed. - -begin - Result := Column; - repeat - Result := GetNextColumn(Result); - until (Result = InvalidColumn) or - ( (coVisible in Items[Result].Options) and - ( (not ConsiderAllowFocus) or - (coAllowFocus in Items[Result].Options) - ) - ); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetPreviousColumn(Column: TColumnIndex): TColumnIndex; - -// Returns the previous column in display order, Column is an index into the columns list. - -var - Position: Integer; - -begin - if Column < 0 then - Result := InvalidColumn - else - begin - Position := Items[Column].Position; - if Position > 0 then - Result := FPositionToIndex[Position - 1] - else - Result := InvalidColumn; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetPreviousVisibleColumn(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex; - -// Returns the previous visible column in display order, Column is an index into the columns list. -// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed. - -begin - Result := Column; - repeat - Result := GetPreviousColumn(Result); - until (Result = InvalidColumn) or - ( (coVisible in Items[Result].Options) and - ( (not ConsiderAllowFocus) or - (coAllowFocus in Items[Result].Options) - ) - ); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetVisibleColumns: TColumnsArray; - -// Returns a list of all currently visible columns in actual order. - -var - I, Counter: Integer; - -begin - SetLength(Result, Count); - Counter := 0; - - for I := 0 to Count - 1 do - if coVisible in Items[FPositionToIndex[I]].Options then - begin - Result[Counter] := Items[FPositionToIndex[I]]; - Inc(Counter); - end; - // Set result length to actual visible count. - SetLength(Result, Counter); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.GetVisibleFixedWidth: Integer; - -// Determines the horizontal space all visible and fixed columns occupy. - -var - I: Integer; - -begin - Result := 0; - for I := 0 to Count - 1 do - begin - if Items[I].Options * [coVisible, coFixed] = [coVisible, coFixed] then - Inc(Result, Items[I].Width); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.IsValidColumn(Column: TColumnIndex): Boolean; - -// Determines whether the given column is valid or not, that is, whether it is one of the current columns. - -begin - Result := (Column > NoColumn) and (Column < Count); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.LoadFromStream(const Stream: TStream; Version: Integer); - -var - I, - ItemCount: Integer; - -begin - Clear; - Stream.ReadBuffer(ItemCount, SizeOf(ItemCount)); - // number of columns - if ItemCount > 0 then - begin - BeginUpdate; - try - for I := 0 to ItemCount - 1 do - Add.LoadFromStream(Stream, Version); - SetLength(FPositionToIndex, ItemCount); - Stream.ReadBuffer(FPositionToIndex[0], ItemCount * SizeOf(TColumnIndex)); - UpdatePositions(True); - finally - EndUpdate; - end; - end; - - // Data introduced with header stream version 5 - if Version > 4 then - Stream.ReadBuffer(FDefaultWidth, SizeOf(FDefaultWidth)); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.PaintHeader(DC: HDC; R: TRect; HOffset: Integer); - -// Backward compatible header paint method. This method takes care of visually moving floating columns - -var - VisibleFixedWidth: Integer; - RTLOffset: Integer; - - procedure PaintFixedArea; - - begin - if VisibleFixedWidth > 0 then - PaintHeader(FHeaderBitmap.Canvas, - Rect(0, 0, Min(R.Right, VisibleFixedWidth), R.Bottom - R.Top), - Point(R.Left, R.Top), RTLOffset); - end; - -begin - // Adjust size of the header bitmap - with TWithSafeRect(FHeader.Treeview.FHeaderRect) do - begin - FHeaderBitmap.SetSize(Max(Right, R.Right - R.Left), Bottom); - end; - - VisibleFixedWidth := GetVisibleFixedWidth; - - // Consider right-to-left directionality. - if FHeader.TreeView.UseRightToLeftAlignment then - RTLOffset := FHeader.Treeview.ComputeRTLOffset - else - RTLOffset := 0; - - if RTLOffset = 0 then - PaintFixedArea; - - // Paint the floating part of the header. - PaintHeader(FHeaderBitmap.Canvas, - Rect(VisibleFixedWidth - HOffset, 0, R.Right + VisibleFixedWidth - HOffset, R.Bottom - R.Top), - Point(R.Left + VisibleFixedWidth, R.Top), RTLOffset); - - // In case of right-to-left directionality we paint the fixed part last. - if RTLOffset <> 0 then - PaintFixedArea; - - // Blit the result to target. - with TWithSafeRect(R) do - BitBlt(DC, Left, Top, Right - Left, Bottom - Top, FHeaderBitmap.Canvas.Handle, Left, Top, SRCCOPY); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.PaintHeader(TargetCanvas: TCanvas; R: TRect; const Target: TPoint; - RTLOffset: Integer = 0); - -// Main paint method to draw the header. -// This procedure will paint the a slice (given in R) out of HeaderRect into TargetCanvas starting at position Target. -// This function does not offer the option to visually move floating columns due to scrolling. To accomplish this you -// need to call this method twice. - -var - Run: TColumnIndex; - RightBorderFlag, - NormalButtonStyle, - NormalButtonFlags, - PressedButtonStyle, - PressedButtonFlags, - RaisedButtonStyle, - RaisedButtonFlags: Cardinal; - Images: TCustomImageList; - OwnerDraw, - AdvancedOwnerDraw: Boolean; - PaintInfo: THeaderPaintInfo; - RequestedElements, - ActualElements: THeaderPaintElements; - - //--------------- local functions ------------------------------------------- - - procedure PrepareButtonStyles; - - // Prepare the button styles and flags for later usage. - - begin - RaisedButtonStyle := 0; - RaisedButtonFlags := 0; - case FHeader.Style of - hsThickButtons: - begin - NormalButtonStyle := BDR_RAISEDINNER or BDR_RAISEDOUTER; - NormalButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_SOFT or BF_ADJUST; - PressedButtonStyle := BDR_RAISEDINNER or BDR_RAISEDOUTER; - PressedButtonFlags := NormalButtonFlags or BF_RIGHT or BF_FLAT or BF_ADJUST; - end; - hsFlatButtons: - begin - NormalButtonStyle := BDR_RAISEDINNER; - NormalButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_ADJUST; - PressedButtonStyle := BDR_SUNKENOUTER; - PressedButtonFlags := BF_RECT or BF_MIDDLE or BF_ADJUST; - end; - else - // hsPlates or hsXPStyle, values are not used in the latter case - begin - NormalButtonStyle := BDR_RAISEDINNER; - NormalButtonFlags := BF_RECT or BF_MIDDLE or BF_SOFT or BF_ADJUST; - PressedButtonStyle := BDR_SUNKENOUTER; - PressedButtonFlags := BF_RECT or BF_MIDDLE or BF_ADJUST; - RaisedButtonStyle := BDR_RAISEDINNER; - RaisedButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_ADJUST; - end; - end; - end; - - //--------------------------------------------------------------------------- - - procedure DrawBackground; - - // Draw the header background. - - var - BackgroundRect: TRect; - Details: TThemedElementDetails; - Theme: HTheme; - begin - BackgroundRect := Rect(Target.X, Target.Y, Target.X + R.Right - R.Left, Target.Y + FHeader.Height); - - with TargetCanvas do - begin - if hpeBackground in RequestedElements then - begin - PaintInfo.PaintRectangle := BackgroundRect; - FHeader.Treeview.DoAdvancedHeaderDraw(PaintInfo, [hpeBackground]); - end - else - begin - if (FHeader.Treeview.VclStyleEnabled and (seClient in FHeader.Treeview.StyleElements)) then - begin - Details := StyleServices.GetElementDetails(thHeaderItemRightNormal); - StyleServices.DrawElement(Handle, Details, BackgroundRect, @BackgroundRect {$IF CompilerVersion >= 34}, FHeader.Treeview.FCurrentPPI{$IFEND}); - end - else - if tsUseThemes in FHeader.Treeview.FStates then - begin - Theme := OpenThemeData(FHeader.Treeview.Handle, 'HEADER'); - DrawThemeBackground(Theme, Handle, HP_HEADERITEM, HIS_NORMAL, BackgroundRect, nil); - CloseThemeData(THeme); - end - else - begin - Brush.Color := FHeader.Background; - FillRect(BackgroundRect); - end; - end; - end; - end; - - //--------------------------------------------------------------------------- - - procedure PaintColumnHeader(AColumn: TColumnIndex; ATargetRect: TRect); - - // Draw a single column to TargetRect. The clipping rect needs to be set before - // this procedure is called. - - var - SavedDC: Integer; - ColCaptionText: string; - ColImageInfo: TVTImageInfo; - Glyph: TThemedHeader; - Details: TThemedElementDetails; - WrapCaption: Boolean; - DrawFormat: Cardinal; - Pos: TRect; - DrawHot: Boolean; - ImageWidth: Integer; - Theme: HTheme; - IdState: Integer; - begin - ColImageInfo.Ghosted := False; - PaintInfo.Column := Items[AColumn]; - with PaintInfo, Column do - begin - IsHoverIndex := (AColumn = FHoverIndex) and (hoHotTrack in FHeader.Options) and (coEnabled in FOptions); - IsDownIndex := (AColumn = FDownIndex) and not FCheckBoxHit; - - if (coShowDropMark in FOptions) and (AColumn = FDropTarget) and (AColumn <> FDragIndex) then - begin - if FDropBefore then - DropMark := dmmLeft - else - DropMark := dmmRight; - end - else - DropMark := dmmNone; - - //Fix for issue 643 - //Do not show the left drop mark if the position to drop is just preceding the target which means - //the dragged column will stay where it is - if (DropMark = dmmLeft) and (Items[FDragIndex].Position = TColumnPosition(Max(Integer(Items[FDropTarget].Position) - 1, 0))) - then - DropMark := dmmNone - else - //Do not show the right drop mark if the position to drop is just following the target which means - //the dragged column will stay where it is - if (DropMark = dmmRight) and (Items[FDragIndex].Position = Items[FDropTarget].Position + 1) - then - DropMark := dmmNone; - - IsEnabled := (coEnabled in FOptions) and (FHeader.Treeview.Enabled); - ShowHeaderGlyph := (hoShowImages in FHeader.Options) and ((Assigned(Images) and (FImageIndex > -1)) or FCheckBox); - ShowSortGlyph := (AColumn = FHeader.SortColumn) and (hoShowSortGlyphs in FHeader.Options); - WrapCaption := coWrapCaption in FOptions; - - PaintRectangle := ATargetRect; - - // This path for text columns or advanced owner draw. - if (Style = vsText) or not OwnerDraw or AdvancedOwnerDraw then - begin - // See if the application wants to draw part of the header itself. - RequestedElements := []; - if AdvancedOwnerDraw then - begin - PaintInfo.Column := Items[AColumn]; - FHeader.Treeview.DoHeaderDrawQueryElements(PaintInfo, RequestedElements); - end; - - if ShowRightBorder or (AColumn < Count - 1) then - RightBorderFlag := BF_RIGHT - else - RightBorderFlag := 0; - - if hpeBackground in RequestedElements then - FHeader.Treeview.DoAdvancedHeaderDraw(PaintInfo, [hpeBackground]) - else - begin - if FHeader.Treeview.VclStyleEnabled and (seClient in FHeader.Treeview.StyleElements) then - begin - if IsDownIndex then - Details := StyleServices.GetElementDetails(thHeaderItemPressed) - else - if IsHoverIndex then - Details := StyleServices.GetElementDetails(thHeaderItemHot) - else - Details := StyleServices.GetElementDetails(thHeaderItemNormal); - StyleServices.DrawElement(TargetCanvas.Handle, Details, PaintRectangle, @PaintRectangle{$IF CompilerVersion >= 34}, FHeader.TreeView.FCurrentPPI{$IFEND}); - end - else - begin - if tsUseThemes in FHeader.Treeview.FStates then - begin - Theme := OpenThemeData(FHeader.Treeview.Handle, 'HEADER'); - if IsDownIndex then - IdState := HIS_PRESSED - else - if IsHoverIndex then - IdState := HIS_HOT - else - IdState := HIS_NORMAL; - DrawThemeBackground(Theme, TargetCanvas.Handle, HP_HEADERITEM, IdState, PaintRectangle, nil); - CloseThemeData(Theme); - end - else - if IsDownIndex then - DrawEdge(TargetCanvas.Handle, PaintRectangle, PressedButtonStyle, PressedButtonFlags) - else - // Plates have the special case of raising on mouse over. - if (FHeader.Style = hsPlates) and IsHoverIndex and - (coAllowClick in FOptions) and (coEnabled in FOptions) then - DrawEdge(TargetCanvas.Handle, PaintRectangle, RaisedButtonStyle, - RaisedButtonFlags or RightBorderFlag) - else - DrawEdge(TargetCanvas.Handle, PaintRectangle, NormalButtonStyle, - NormalButtonFlags or RightBorderFlag); - end; - end; - - PaintRectangle := ATargetRect; - - // calculate text and glyph position - InflateRect(PaintRectangle, -2, -2); - DrawFormat := DT_TOP or DT_NOPREFIX; - case CaptionAlignment of - taLeftJustify : DrawFormat := DrawFormat or DT_LEFT; - taRightJustify : DrawFormat := DrawFormat or DT_RIGHT; - taCenter : DrawFormat := DrawFormat or DT_CENTER; - end; - if UseRightToLeftReading then - DrawFormat := DrawFormat + DT_RTLREADING; - ComputeHeaderLayout(PaintInfo, DrawFormat); - - // Move glyph and text one pixel to the right and down to simulate a pressed button. - if IsDownIndex then - begin - OffsetRect(TextRectangle, 1, 1); - Inc(GlyphPos.X); - Inc(GlyphPos.Y); - Inc(SortGlyphPos.X); - Inc(SortGlyphPos.Y); - end; - - // Advanced owner draw allows to paint elements, which would normally not be painted (because of space - // limitations, empty captions etc.). - ActualElements := RequestedElements * [hpeHeaderGlyph, hpeSortGlyph, hpeDropMark, hpeText, hpeOverlay]; - - // main glyph - FHasImage := False; - if Assigned(Images) then - ImageWidth := Images.Width - else - ImageWidth := 0; - - if not (hpeHeaderGlyph in ActualElements) and ShowHeaderGlyph and - (not ShowSortGlyph or (FBiDiMode <> bdLeftToRight) or (GlyphPos.X + ImageWidth <= SortGlyphPos.X) ) then - begin - if not FCheckBox then - begin - ColImageInfo.Images := Images; - Images.Draw(TargetCanvas, GlyphPos.X, GlyphPos.Y, FImageIndex, IsEnabled); - end - else - begin - with Header.Treeview do - begin - ColImageInfo.Images := FCheckImages; - ColImageInfo.Index := GetCheckImage(nil, FCheckType, FCheckState, IsEnabled); - ColImageInfo.XPos := GlyphPos.X; - ColImageInfo.YPos := GlyphPos.Y; - PaintCheckImage(TargetCanvas, ColImageInfo, False); - end; - end; - - FHasImage := True; - with TWithSafeRect(FImageRect) do - begin - Left := GlyphPos.X; - Top := GlyphPos.Y; - Right := Left + ColImageInfo.Images.Width; - Bottom := Top + ColImageInfo.Images.Height; - end; - end; - - // caption - if WrapCaption then - ColCaptionText := FCaptionText - else - ColCaptionText := Text; - if IsHoverIndex and FHeader.Treeview.VclStyleEnabled then - DrawHot := True - else - DrawHot := (IsHoverIndex and (hoHotTrack in FHeader.Options) and not(tsUseThemes in FHeader.Treeview.FStates)); - if not(hpeText in ActualElements) and (Length(Text) > 0) then - DrawButtonText(TargetCanvas.Handle, ColCaptionText, TextRectangle, IsEnabled, DrawHot, DrawFormat, WrapCaption); - - // sort glyph - if not (hpeSortGlyph in ActualElements) and ShowSortGlyph then - begin - if tsUseExplorerTheme in FHeader.Treeview.FStates then - begin - Pos.TopLeft := SortGlyphPos; - Pos.Right := Pos.Left + SortGlyphSize.cx; - Pos.Bottom := Pos.Top + SortGlyphSize.cy; - if FHeader.SortDirection = sdAscending then - Glyph := thHeaderSortArrowSortedUp - else - Glyph := thHeaderSortArrowSortedDown; - Details := StyleServices.GetElementDetails(Glyph); - if not StyleServices.DrawElement(TargetCanvas.Handle, Details, Pos, @Pos {$IF CompilerVersion >= 34}, FHeader.TreeView.FCurrentPPI {$IFEND}) then - PaintInfo.DrawSortArrow(FHeader.SortDirection); - end - else - begin - PaintInfo.DrawSortArrow(FHeader.SortDirection); - end; - end; - - // Show an indication if this column is the current drop target in a header drag operation. - if not (hpeDropMark in ActualElements) and (DropMark <> dmmNone) then - begin - PaintInfo.DrawDropMark(); - end; - - if ActualElements <> [] then - begin - SavedDC := SaveDC(TargetCanvas.Handle); - FHeader.Treeview.DoAdvancedHeaderDraw(PaintInfo, ActualElements); - RestoreDC(TargetCanvas.Handle, SavedDC); - end; - end - else // Let application draw the header. - FHeader.Treeview.DoHeaderDraw(TargetCanvas, Items[AColumn], PaintRectangle, IsHoverIndex, IsDownIndex, - DropMark); - end; - end; - - //--------------- end local functions --------------------------------------- - -var - TargetRect: TRect; - MaxX: Integer; - -begin - if IsRectEmpty(R) then - Exit; - - // If both draw posibillities are specified then prefer the advanced way. - AdvancedOwnerDraw := (hoOwnerDraw in FHeader.Options) and Assigned(FHeader.Treeview.OnAdvancedHeaderDraw) and - Assigned(FHeader.Treeview.FOnHeaderDrawQueryElements) and not (csDesigning in FHeader.Treeview.ComponentState); - OwnerDraw := (hoOwnerDraw in FHeader.Options) and Assigned(FHeader.Treeview.OnHeaderDraw) and - not (csDesigning in FHeader.Treeview.ComponentState) and not AdvancedOwnerDraw; - - ZeroMemory(@PaintInfo, SizeOf(PaintInfo)); - PaintInfo.TargetCanvas := TargetCanvas; - - with PaintInfo, TargetCanvas do - begin - // Use shortcuts for the images and the font. - Images := FHeader.Images; - Font := FHeader.Font; - - PrepareButtonStyles; - - // At first, query the application which parts of the header it wants to draw on its own. - RequestedElements := []; - if AdvancedOwnerDraw then - begin - PaintRectangle := R; - Column := nil; - FHeader.Treeview.DoHeaderDrawQueryElements(PaintInfo, RequestedElements); - end; - - // Draw the background. - DrawBackground; - - // Now that we have drawn the background, we apply the header's dimensions to R. - R := Rect(Max(R.Left, 0), Max(R.Top, 0), Min(R.Right, TotalWidth), Min(R.Bottom, Header.Height)); - - // Determine where to stop. - MaxX := Target.X + R.Right - R.Left - //Fixes issues #544, #427 -- MaxX should also shift on BidiMode bdRightToLeft - + RTLOffset; //added for fix - - // Determine the start column. - Run := ColumnFromPosition(Point(R.Left + RTLOffset, 0), False); - if Run <= NoColumn then - Exit; - - TargetRect.Top := Target.Y; - TargetRect.Bottom := Target.Y + R.Bottom - R.Top; - TargetRect.Left := Target.X - R.Left + Items[Run].FLeft + RTLOffset; - // TargetRect.Right will be set in the loop - - ShowRightBorder := (FHeader.Style = hsThickButtons) or not (hoAutoResize in FHeader.Options) or - (FHeader.Treeview.BevelKind = bkNone); - - // Now go for each button. - while (Run > NoColumn) and (TargetRect.Left < MaxX) do - begin - TargetRect.Right := TargetRect.Left + Items[Run].Width; - - // create a clipping rect to limit painting to button area - ClipCanvas(TargetCanvas, Rect(Max(TargetRect.Left, Target.X), Target.Y + R.Top, - Min(TargetRect.Right, MaxX), TargetRect.Bottom)); - - PaintColumnHeader(Run, TargetRect); - - SelectClipRgn(Handle, 0); - - TargetRect.Left := TargetRect.Right; - Run := GetNextVisibleColumn(Run); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualTreeColumns.SaveToStream(const Stream: TStream); - -var - I: Integer; - -begin - I := Count; - Stream.WriteBuffer(I, SizeOf(I)); - if I > 0 then - begin - for I := 0 to Count - 1 do - TVirtualTreeColumn(Items[I]).SaveToStream(Stream); - - Stream.WriteBuffer(FPositionToIndex[0], Count * SizeOf(TColumnIndex)); - end; - - // Data introduced with header stream version 5. - Stream.WriteBuffer(DefaultWidth, SizeOf(DefaultWidth)); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualTreeColumns.TotalWidth: Integer; - -var - LastColumn: TColumnIndex; - -begin - Result := 0; - if (Count > 0) and (Length(FPositionToIndex) > 0) then - begin - LastColumn := FPositionToIndex[Count - 1]; - if not (coVisible in Items[LastColumn].Options) then - LastColumn := GetPreviousVisibleColumn(LastColumn); - if LastColumn > NoColumn then - with Items[LastColumn] do - Result := FLeft + FWidth; - end; -end; - -//----------------- TVTFixedAreaConstraints ---------------------------------------------------------------------------- - -constructor TVTFixedAreaConstraints.Create(AOwner: TVTHeader); - -begin - inherited Create; - FMaxWidthPercent := 95; - FHeader := AOwner; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTFixedAreaConstraints.SetConstraints(Index: Integer; Value: TVTConstraintPercent); - -begin - case Index of - 0: - if Value <> FMaxHeightPercent then - begin - FMaxHeightPercent := Value; - if (Value > 0) and (Value < FMinHeightPercent) then - FMinHeightPercent := Value; - Change; - end; - 1: - if Value <> FMaxWidthPercent then - begin - FMaxWidthPercent := Value; - if (Value > 0) and (Value < FMinWidthPercent) then - FMinWidthPercent := Value; - Change; - end; - 2: - if Value <> FMinHeightPercent then - begin - FMinHeightPercent := Value; - if (FMaxHeightPercent > 0) and (Value > FMaxHeightPercent) then - FMaxHeightPercent := Value; - Change; - end; - 3: - if Value <> FMinWidthPercent then - begin - FMinWidthPercent := Value; - if (FMaxWidthPercent > 0) and (Value > FMaxWidthPercent) then - FMaxWidthPercent := Value; - Change; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTFixedAreaConstraints.Change; - -begin - if Assigned(FOnChange) then - FOnChange(Self); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTFixedAreaConstraints.Assign(Source: TPersistent); - -begin - if Source is TVTFixedAreaConstraints then - begin - FMaxHeightPercent := TVTFixedAreaConstraints(Source).FMaxHeightPercent; - FMaxWidthPercent := TVTFixedAreaConstraints(Source).FMaxWidthPercent; - FMinHeightPercent := TVTFixedAreaConstraints(Source).FMinHeightPercent; - FMinWidthPercent := TVTFixedAreaConstraints(Source).FMinWidthPercent; - Change; - end - else - inherited; -end; - -//----------------- TVTHeader ----------------------------------------------------------------------------------------- - -constructor TVTHeader.Create(AOwner: TBaseVirtualTree); - -begin - inherited Create; - FOwner := AOwner; - FColumns := GetColumnsClass.Create(Self); - FHeight := 19; - FDefaultHeight := FHeight; - FMinHeight := 10; - FMaxHeight := 10000; - FFont := TFont.Create; - FFont.OnChange := FontChanged; - FParentFont := True; - FBackgroundColor := clBtnFace; - FOptions := [hoColumnResize, hoDrag, hoShowSortGlyphs]; - - FImageChangeLink := TChangeLink.Create; - FImageChangeLink.OnChange := ImageListChange; - - FSortColumn := NoColumn; - FSortDirection := sdAscending; - FMainColumn := NoColumn; - - FDragImage := TVTDragImage.Create(AOwner); - with FDragImage do - begin - Fade := False; - PreBlendBias := -50; - Transparency := 140; - end; - - fSplitterHitTolerance := 8; - FFixedAreaConstraints := TVTFixedAreaConstraints.Create(Self); - FFixedAreaConstraints.OnChange := FixedAreaConstraintsChanged; - - FDoingAutoFitColumns := false; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -destructor TVTHeader.Destroy; - -begin - FDragImage.Free; - FFixedAreaConstraints.Free; - FImageChangeLink.Free; - FFont.Free; - FColumns.Clear; // TCollection's Clear method is not virtual, so we have to call our own Clear method manually. - FColumns.Free; - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.FontChanged(Sender: TObject); -begin - inherited; - {$IF CompilerVersion < 31} // See issue #1043 - AutoScale(); - {$IFEND} -end; - -procedure TVTHeader.AutoScale(); -var - I: Integer; - lMaxHeight: Integer; -begin - if toAutoChangeScale in Treeview.TreeOptions.AutoOptions then - begin - // Ensure a minimum header size based on the font, so that all text is visible. - // First find the largest Columns[].Spacing - lMaxHeight := 0; - for I := 0 to Self.Columns.Count - 1 do - lMaxHeight := Max(lMaxHeight, Columns[I].Spacing); - // Calculate the required height based on the font, this is important as the user might just have increased the size of the system icon font. - with TBitmap.Create do - try - Canvas.Font.Assign(FFont); - lMaxHeight := lMaxHeight {top spacing} + (lMaxHeight div 2) {minimum bottom spacing} + Canvas.TextHeight('Q'); - finally - Free; - end; - // Get the maximum of the scaled original value and the minimum needed header height. - lMaxHeight := Max(lMaxHeight, FHeight); - // Set the calculated size - Self.SetHeight(lMaxHeight); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.GetMainColumn: TColumnIndex; - -begin - if FColumns.Count > 0 then - Result := FMainColumn - else - Result := NoColumn; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.GetUseColumns: Boolean; - -begin - Result := FColumns.Count > 0; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.IsFontStored: Boolean; - -begin - Result := not ParentFont; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetAutoSizeIndex(Value: TColumnIndex); - -begin - if FAutoSizeIndex <> Value then - begin - FAutoSizeIndex := Value; - if hoAutoResize in FOptions then - Columns.AdjustAutoSize(InvalidColumn); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetBackground(Value: TColor); - -begin - if FBackgroundColor <> Value then - begin - FBackgroundColor := Value; - Invalidate(nil); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetColumns(Value: TVirtualTreeColumns); - -begin - FColumns.Assign(Value); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetDefaultHeight(Value: Integer); - -begin - if Value < FMinHeight then - Value := FMinHeight; - if Value > FMaxHeight then - Value := FMaxHeight; - - if FHeight = FDefaultHeight then - SetHeight(Value); - FDefaultHeight := Value; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetFont(const Value: TFont); - -begin - FFont.Assign(Value); - FParentFont := False; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetHeight(Value: Integer); - -var - RelativeMaxHeight, - RelativeMinHeight, - EffectiveMaxHeight, - EffectiveMinHeight: Integer; - -begin - if not TreeView.HandleAllocated then - begin - FHeight := Value; - Include(FStates, hsNeedScaling); - end - else - begin - with FFixedAreaConstraints do - begin - RelativeMaxHeight := ((Treeview.ClientHeight + FHeight) * FMaxHeightPercent) div 100; - RelativeMinHeight := ((Treeview.ClientHeight + FHeight) * FMinHeightPercent) div 100; - - EffectiveMinHeight := IfThen(FMaxHeightPercent > 0, Min(RelativeMaxHeight, FMinHeight), FMinHeight); - EffectiveMaxHeight := IfThen(FMinHeightPercent > 0, Max(RelativeMinHeight, FMaxHeight), FMaxHeight); - - Value := Min(Max(Value, EffectiveMinHeight), EffectiveMaxHeight); - if FMinHeightPercent > 0 then - Value := Max(RelativeMinHeight, Value); - if FMaxHeightPercent > 0 then - Value := Min(RelativeMaxHeight, Value); - end; - - if FHeight <> Value then - begin - FHeight := Value; - if not (csLoading in Treeview.ComponentState) and not (hsScaling in FStates) then - RecalculateHeader; - Treeview.Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetImages(const Value: TCustomImageList); - -begin - if FImages <> Value then - begin - if Assigned(FImages) then - begin - FImages.UnRegisterChanges(FImageChangeLink); - FImages.RemoveFreeNotification(FOwner); - end; - FImages := Value; - if Assigned(FImages) then - begin - FImages.RegisterChanges(FImageChangeLink); - FImages.FreeNotification(FOwner); - end; - if not (csLoading in Treeview.ComponentState) then - Invalidate(nil); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetMainColumn(Value: TColumnIndex); - -begin - if csLoading in Treeview.ComponentState then - FMainColumn := Value - else - begin - if Value < 0 then - Value := 0; - if Value > FColumns.Count - 1 then - Value := FColumns.Count - 1; - if Value <> FMainColumn then - begin - FMainColumn := Value; - if not (csLoading in Treeview.ComponentState) then - begin - Treeview.MainColumnChanged; - if not (toExtendedFocus in Treeview.TreeOptions.SelectionOptions) then - Treeview.FocusedColumn := FMainColumn; - Treeview.Invalidate; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetMaxHeight(Value: Integer); - -begin - if Value < FMinHeight then - Value := FMinHeight; - FMaxHeight := Value; - SetHeight(FHeight); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetMinHeight(Value: Integer); - -begin - if Value < 0 then - Value := 0; - if Value > FMaxHeight then - Value := FMaxHeight; - FMinHeight := Value; - SetHeight(FHeight); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetOptions(Value: TVTHeaderOptions); - -var - ToBeSet, - ToBeCleared: TVTHeaderOptions; - -begin - ToBeSet := Value - FOptions; - ToBeCleared := FOptions - Value; - FOptions := Value; - - if (hoAutoResize in (ToBeSet + ToBeCleared)) and (FColumns.Count > 0) then - begin - FColumns.AdjustAutoSize(InvalidColumn); - if Treeview.HandleAllocated then - begin - Treeview.UpdateHorizontalScrollBar(False); - if hoAutoResize in ToBeSet then - Treeview.Invalidate; - end; - end; - - if not (csLoading in Treeview.ComponentState) and Treeview.HandleAllocated then - begin - if hoVisible in (ToBeSet + ToBeCleared) then - RecalculateHeader; - Invalidate(nil); - Treeview.Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetParentFont(Value: Boolean); - -begin - if FParentFont <> Value then - begin - FParentFont := Value; - if FParentFont then - FFont.Assign(FOwner.Font); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetSortColumn(Value: TColumnIndex); - -begin - if csLoading in Treeview.ComponentState then - FSortColumn := Value - else - DoSetSortColumn(Value, FSortDirection); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetSortDirection(const Value: TSortDirection); - -begin - if Value <> FSortDirection then - begin - FSortDirection := Value; - Invalidate(nil); - if ((toAutoSort in Treeview.TreeOptions.AutoOptions) or (hoHeaderClickAutoSort in Options)) and (Treeview.UpdateCount = 0) then - Treeview.SortTree(FSortColumn, FSortDirection, True); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.CanSplitterResize(P: TPoint): Boolean; - -begin - Result := hoHeightResize in FOptions; - DoCanSplitterResize(P, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SetStyle(Value: TVTHeaderStyle); - -begin - if FStyle <> Value then - begin - FStyle := Value; - if not (csLoading in Treeview.ComponentState) then - Invalidate(nil); - end; -end; - -procedure TVTHeader.StyleChanged(); -begin - {$IF CompilerVersion < 31} // See issue #1043 - AutoScale(); //Elements may have changed in size - {$IFEND} -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.CanWriteColumns: Boolean; - -// descendants may override this to optionally prevent column writing (e.g. if they are build dynamically). - -begin - Result := True; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.ChangeScale(M, D: Integer; isDpiChange: Boolean); -var - I: Integer; -begin - // This method is only executed if toAutoChangeScale is set - FMinHeight := MulDiv(FMinHeight, M, D); - FMaxHeight := MulDiv(FMaxHeight, M, D); - Self.Height := MulDiv(FHeight, M, D); - if not ParentFont then - Font.Height := MulDiv(Font.Height, M, D); - // Scale the columns widths too - for I := 0 to FColumns.Count - 1 do - Self.FColumns[I].ChangeScale(M, D, isDpiChange); - if not isDpiChange then - AutoScale(); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.DetermineSplitterIndex(P: TPoint): Boolean; - -// Tries to find the index of that column whose right border corresponds to P. -// Result is True if column border was hit (with -3..+5 pixels tolerance). -// For continuous resizing the current track index and the column's left/right border are set. -// Note: The hit test is checking from right to left (or left to right in RTL mode) to make enlarging of zero-sized -// columns possible. - -var - VisibleFixedWidth: Integer; - SplitPoint: Integer; - - //--------------- local function -------------------------------------------- - - function IsNearBy(IsFixedCol: Boolean; LeftTolerance, RightTolerance: Integer): Boolean; - - begin - if IsFixedCol then - Result := (P.X < SplitPoint + Treeview.FEffectiveOffsetX + RightTolerance) and (P.X > SplitPoint + Treeview.FEffectiveOffsetX - LeftTolerance) - else - Result := (P.X > VisibleFixedWidth) and (P.X < SplitPoint + RightTolerance) and (P.X > SplitPoint - LeftTolerance); - end; - - //--------------- end local function ---------------------------------------- - -var - I: Integer; - LeftTolerance: Integer; // The area left of the column divider which allows column resizing -begin - Result := False; - - if FColumns.Count > 0 then - begin - FColumns.TrackIndex := NoColumn; - VisibleFixedWidth := FColumns.GetVisibleFixedWidth; - LeftTolerance := Round(SplitterHitTolerance * 0.6); - if Treeview.UseRightToLeftAlignment then - begin - SplitPoint := -Treeview.FEffectiveOffsetX; - if FColumns.TotalWidth < Treeview.ClientWidth then - Inc(SplitPoint, Treeview.ClientWidth - FColumns.TotalWidth); - - for I := 0 to FColumns.Count - 1 do - with FColumns, Items[FPositionToIndex[I]] do - if coVisible in FOptions then - begin - if IsNearBy(coFixed in FOptions, LeftTolerance, SplitterHitTolerance - LeftTolerance) then - begin - if CanSplitterResize(P, FPositionToIndex[I]) then - begin - Result := True; - FTrackIndex := FPositionToIndex[I]; - - // Keep the right border of this column. This and the current mouse position - // directly determine the current column width. - FTrackPoint.X := SplitPoint + IfThen(coFixed in FOptions, Treeview.FEffectiveOffsetX) + FWidth; - FTrackPoint.Y := P.Y; - Break; - end; - end; - Inc(SplitPoint, FWidth); - end; - end - else - begin - SplitPoint := -Treeview.FEffectiveOffsetX + FColumns.TotalWidth; - - for I := FColumns.Count - 1 downto 0 do - with FColumns, Items[FPositionToIndex[I]] do - if coVisible in FOptions then - begin - if IsNearBy(coFixed in FOptions, SplitterHitTolerance - LeftTolerance, LeftTolerance) then - begin - if CanSplitterResize(P, FPositionToIndex[I]) then - begin - Result := True; - FTrackIndex := FPositionToIndex[I]; - - // Keep the left border of this column. This and the current mouse position - // directly determine the current column width. - FTrackPoint.X := SplitPoint + IfThen(coFixed in FOptions, Treeview.FEffectiveOffsetX) - FWidth; - FTrackPoint.Y := P.Y; - Break; - end; - end; - Dec(SplitPoint, FWidth); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.DoAfterAutoFitColumn(Column: TColumnIndex); - -begin - if Assigned(TreeView.FOnAfterAutoFitColumn) then - TreeView.FOnAfterAutoFitColumn(Self, Column); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.DoAfterColumnWidthTracking(Column: TColumnIndex); - -// Tell the application that a column width tracking operation has been finished. - -begin - if Assigned(TreeView.FOnAfterColumnWidthTracking) then - TreeView.FOnAfterColumnWidthTracking(Self, Column); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.DoAfterHeightTracking; - -// Tell the application that a height tracking operation has been finished. - -begin - if Assigned(TreeView.FOnAfterHeaderHeightTracking) then - TreeView.FOnAfterHeaderHeightTracking(Self); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.DoBeforeAutoFitColumn(Column: TColumnIndex; SmartAutoFitType: TSmartAutoFitType): Boolean; - -// Query the application if we may autofit a column. - -begin - Result := True; - if Assigned(TreeView.FOnBeforeAutoFitColumn) then - TreeView.FOnBeforeAutoFitColumn(Self, Column, SmartAutoFitType, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.DoBeforeColumnWidthTracking(Column: TColumnIndex; Shift: TShiftState); - -// Tell the a application that a column width tracking operation may begin. - -begin - if Assigned(TreeView.FOnBeforeColumnWidthTracking) then - TreeView.FOnBeforeColumnWidthTracking(Self, Column, Shift); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.DoBeforeHeightTracking(Shift: TShiftState); - -// Tell the application that a height tracking operation may begin. - -begin - if Assigned(TreeView.FOnBeforeHeaderHeightTracking) then - TreeView.FOnBeforeHeaderHeightTracking(Self, Shift); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.DoCanSplitterResize(P: TPoint; var Allowed: Boolean); -begin - if Assigned(TreeView.FOnCanSplitterResizeHeader) then - TreeView.FOnCanSplitterResizeHeader(Self, P, Allowed); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.DoColumnWidthDblClickResize(Column: TColumnIndex; P: TPoint; Shift: TShiftState): Boolean; - -// Queries the application whether a double click on the column splitter should resize the column. - -begin - Result := True; - if Assigned(TreeView.FOnColumnWidthDblClickResize) then - TreeView.FOnColumnWidthDblClickResize(Self, Column, Shift, P, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.DoColumnWidthTracking(Column: TColumnIndex; Shift: TShiftState; var TrackPoint: TPoint; P: TPoint): Boolean; - -begin - Result := True; - if Assigned(TreeView.FOnColumnWidthTracking) then - TreeView.FOnColumnWidthTracking(Self, Column, Shift, TrackPoint, P, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.DoGetPopupMenu(Column: TColumnIndex; Position: TPoint): TPopupMenu; - -// Queries the application whether there is a column specific header popup menu. - -var - AskParent: Boolean; - -begin - Result := PopupMenu; - if Assigned(TreeView.FOnGetPopupMenu) then - TreeView.FOnGetPopupMenu(TreeView, nil, Column, Position, AskParent, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.DoHeightTracking(var P: TPoint; Shift: TShiftState): Boolean; - -begin - Result := True; - if Assigned(TreeView.FOnHeaderHeightTracking) then - TreeView.FOnHeaderHeightTracking(Self, P, Shift, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.DoHeightDblClickResize(var P: TPoint; Shift: TShiftState): Boolean; - -begin - Result := True; - if Assigned(TreeView.FOnHeaderHeightDblClickResize) then - TreeView.FOnHeaderHeightDblClickResize(Self, P, Shift, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.DoSetSortColumn(Value: TColumnIndex; pSortDirection: TSortDirection); - -begin - if Value < NoColumn then - Value := NoColumn; - if Value > Columns.Count - 1 then - Value := Columns.Count - 1; - if FSortColumn <> Value then - begin - if FSortColumn > NoColumn then - Invalidate(Columns[FSortColumn]); - FSortColumn := Value; - FSortDirection := pSortDirection; - if FSortColumn > NoColumn then - Invalidate(Columns[FSortColumn]); - if ((toAutoSort in Treeview.TreeOptions.AutoOptions) or (hoHeaderClickAutoSort in Options)) and (Treeview.UpdateCount = 0) then - Treeview.SortTree(FSortColumn, FSortDirection, True); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.DragTo(P: TPoint); - -// Moves the drag image to a new position, which is determined from the passed point P and the previous -// mouse position. - -var - I, - NewTarget: Integer; - // optimized drag image move support - ClientP: TPoint; - Left, - Right: Integer; - NeedRepaint: Boolean; // True if the screen needs an update (changed drop target or drop side) - -begin - // Determine new drop target and which side of it is prefered. - ClientP := Treeview.ScreenToClient(P); - // Make coordinates relative to (0, 0) of the non-client area. - Inc(ClientP.Y, FHeight); - NewTarget := FColumns.ColumnFromPosition(ClientP); - NeedRepaint := (NewTarget <> InvalidColumn) and (NewTarget <> FColumns.DropTarget); - if NewTarget >= 0 then - begin - FColumns.GetColumnBounds(NewTarget, Left, Right); - if (ClientP.X < ((Left + Right) div 2)) <> FColumns.DropBefore then - begin - NeedRepaint := True; - FColumns.DropBefore := not FColumns.DropBefore; - end; - end; - - if NeedRepaint then - begin - // Invalidate columns which need a repaint. - if FColumns.DropTarget > NoColumn then - begin - I := FColumns.DropTarget; - FColumns.DropTarget := NoColumn; - Invalidate(FColumns.Items[I]); - end; - if (NewTarget > NoColumn) and (NewTarget <> FColumns.DropTarget) then - begin - Invalidate(FColumns.Items[NewTarget]); - FColumns.DropTarget := NewTarget; - end; - end; - - // Fix for various problems mentioned in issue 248. - if NeedRepaint then - begin - UpdateWindow(FOwner.Handle); - // The new routine recaptures the backup image after the updatewindow - // Note: We could have called this unconditionally but when called - // over the tree, doesn't capture the background image. Since our - // problems are in painting of the header, we call it only when the - // drag image is over the header. - if - // determine the case when the drag image is or was on the header area - (InHeader(FOwner.ScreenToClient(FDragImage.LastPosition)) - or InHeader(FOwner.ScreenToClient(FDragImage.ImagePosition)) - ) then - begin - GDIFlush; - FOwner.UpdateWindowAndDragImage(FOwner, FOwner.HeaderRect, True, true); - end; - // since we took care of UpdateWindow above, there is no need to do an - // update window again by sending NeedRepaint. So switch off the second parameter. - NeedRepaint := false; - end; - - FDragImage.DragTo(P, NeedRepaint); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.FixedAreaConstraintsChanged(Sender: TObject); - -// This method gets called when FFixedAreaConstraints is changed. - -begin - if Treeview.HandleAllocated then - RescaleHeader - else - Include(FStates, hsNeedScaling); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.GetColumnsClass: TVirtualTreeColumnsClass; - -// Returns the class to be used for the actual column implementation. descendants may optionally override this and -// return their own class. - -begin - Result := TVirtualTreeColumns; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.GetOwner: TPersistent; - -begin - Result := FOwner; -end; - -function TVTHeader.GetRestoreSelectionColumnIndex: Integer; -begin - if fRestoreSelectionColumnIndex >= 0 then - Result := fRestoreSelectionColumnIndex - else - Result := MainColumn; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.GetShiftState: TShiftState; - -begin - Result := []; - if GetKeyState(VK_SHIFT) < 0 then - Include(Result, ssShift); - if GetKeyState(VK_CONTROL) < 0 then - Include(Result, ssCtrl); - if GetKeyState(VK_MENU) < 0 then - Include(Result, ssAlt); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.HandleHeaderMouseMove(var Message: TWMMouseMove): Boolean; - -var - P: TPoint; - NextColumn, - I: TColumnIndex; - NewWidth: Integer; - -begin - Result := False; - with Message do - begin - P := Point(XPos, YPos); - if hsColumnWidthTrackPending in FStates then - begin - Treeview.StopTimer(HeaderTimer); - FStates := FStates - [hsColumnWidthTrackPending] + [hsColumnWidthTracking]; - HandleHeaderMouseMove := True; - Result := 0; - end - else - if hsHeightTrackPending in FStates then - begin - Treeview.StopTimer(HeaderTimer); - FStates := FStates - [hsHeightTrackPending] + [hsHeightTracking]; - HandleHeaderMouseMove := True; - Result := 0; - end - else - if hsColumnWidthTracking in FStates then - begin - if DoColumnWidthTracking(FColumns.TrackIndex, GetShiftState, FTrackPoint, P) then - begin - if Treeview.UseRightToLeftAlignment then - begin - NewWidth := FTrackPoint.X - XPos; - NextColumn := FColumns.GetPreviousVisibleColumn(FColumns.TrackIndex); - end - else - begin - NewWidth := XPos - FTrackPoint.X; - NextColumn := FColumns.GetNextVisibleColumn(FColumns.TrackIndex); - end; - - // The autosized column cannot be resized using the mouse normally. Instead we resize the next - // visible column, so it look as we directly resize the autosized column. - if (hoAutoResize in FOptions) and (FColumns.TrackIndex = FAutoSizeIndex) and - (NextColumn > NoColumn) and (coResizable in FColumns[NextColumn].Options) and - (FColumns[FColumns.TrackIndex].MinWidth < NewWidth) and - (FColumns[FColumns.TrackIndex].MaxWidth > NewWidth) then - FColumns[NextColumn].Width := FColumns[NextColumn].Width - NewWidth - + FColumns[FColumns.TrackIndex].Width - else - FColumns[FColumns.TrackIndex].Width := NewWidth; // 1 EListError seen here (List index out of bounds (-1)) since 10/2013 - end; - HandleHeaderMouseMove := True; - Result := 0; - end - else - if hsHeightTracking in FStates then - begin - if DoHeightTracking(P, GetShiftState) then - SetHeight(Integer(FHeight) + P.Y); - HandleHeaderMouseMove := True; - Result := 0; - end - else - begin - if hsDragPending in FStates then - begin - P := Treeview.ClientToScreen(P); - // start actual dragging if allowed - if (hoDrag in FOptions) and Treeview.DoHeaderDragging(FColumns.DownIndex) then - begin - if ((Abs(FDragStart.X - P.X) > Mouse.DragThreshold) or - (Abs(FDragStart.Y - P.Y) > Mouse.DragThreshold)) then - begin - Treeview.StopTimer(HeaderTimer); - I := FColumns.DownIndex; - FColumns.DownIndex := NoColumn; - FColumns.HoverIndex := NoColumn; - if I > NoColumn then - Invalidate(FColumns[I]); - PrepareDrag(P, FDragStart); - FStates := FStates - [hsDragPending] + [hsDragging]; - HandleHeaderMouseMove := True; - Result := 0; - end; - end; - end - else - if hsDragging in FStates then - begin - DragTo(Treeview.ClientToScreen(Point(XPos, YPos))); - HandleHeaderMouseMove := True; - Result := 0; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.HandleMessage(var Message: TMessage): Boolean; - -// The header gets here the opportunity to handle certain messages before they reach the tree. This is important -// because the tree needs to handle various non-client area messages for the header as well as some dragging/tracking -// events. -// By returning True the message will not be handled further, otherwise the message is then dispatched -// to the proper message handlers. - -var - P: TPoint; - R: TRect; - I: TColumnIndex; - OldPosition: Integer; - HitIndex: TColumnIndex; - NewCursor: HCURSOR; - Button: TMouseButton; - IsInHeader, - IsHSplitterHit, - IsVSplitterHit: Boolean; - - //--------------- local function -------------------------------------------- - - function HSplitterHit: Boolean; - begin - Result := (hoColumnResize in FOptions) and DetermineSplitterIndex(P); - if Result and not InHeader(P) then - begin - // Code commented due to issue #1067. What was the orginal inention of this code? It does not make much sense unless you allow column resize outside the header. - // NextCol := FColumns.GetNextVisibleColumn(FColumns.TrackIndex); - // if not (coFixed in FColumns[FColumns.TrackIndex].Options) or (NextCol <= NoColumn) or - // (coFixed in FColumns[NextCol].Options) or (P.Y > Integer(Treeview.FRangeY)) then - Result := False; - end; - end; - - //--------------- end local function ---------------------------------------- - -begin - Result := False; - case Message.Msg of - WM_SIZE: - begin - if not (tsWindowCreating in FOwner.TreeStates) then - if (hoAutoResize in FOptions) and not (hsAutoSizing in FStates) then - begin - FColumns.AdjustAutoSize(InvalidColumn); - Invalidate(nil); - end - else - if not (hsScaling in FStates) then - begin - RescaleHeader; - Invalidate(nil); - end; - end; - CM_PARENTFONTCHANGED: - if FParentFont then - FFont.Assign(FOwner.Font); - CM_BIDIMODECHANGED: - for I := 0 to FColumns.Count - 1 do - if coParentBiDiMode in FColumns[I].Options then - FColumns[I].ParentBiDiModeChanged; - WM_NCMBUTTONDOWN: - begin - with TWMNCMButtonDown(Message) do - P := Treeview.ScreenToClient(Point(XCursor, YCursor)); - if InHeader(P) then - FOwner.DoHeaderMouseDown(mbMiddle, GetShiftState, P.X, P.Y + Integer(FHeight)); - end; - WM_NCMBUTTONUP: - begin - with TWMNCMButtonUp(Message) do - P := FOwner.ScreenToClient(Point(XCursor, YCursor)); - if InHeader(P) then - begin - FColumns.HandleClick(P, mbMiddle, True, False); - FOwner.DoHeaderMouseUp(mbMiddle, GetShiftState, P.X, P.Y + Integer(FHeight)); - FColumns.DownIndex := NoColumn; - FColumns.CheckBoxHit := False; - end; - end; - WM_LBUTTONDBLCLK, - WM_NCLBUTTONDBLCLK, - WM_NCMBUTTONDBLCLK, - WM_NCRBUTTONDBLCLK: - begin - if Message.Msg <> WM_LBUTTONDBLCLK then - with TWMNCLButtonDblClk(Message) do - P := FOwner.ScreenToClient(Point(XCursor, YCursor)) - else - with TWMLButtonDblClk(Message) do - P := Point(XPos, YPos); - - if (hoHeightDblClickResize in FOptions) and InHeaderSplitterArea(P) and (FDefaultHeight > 0) then - begin - if DoHeightDblClickResize(P, GetShiftState) and (FDefaultHeight > 0) then - SetHeight(FMinHeight); - Result := True; - end - else - if HSplitterHit and ((Message.Msg = WM_NCLBUTTONDBLCLK) or (Message.Msg = WM_LBUTTONDBLCLK)) and - (hoDblClickResize in FOptions) and (FColumns.TrackIndex > NoColumn) then - begin - // If the click was on a splitter then resize column to smallest width. - if DoColumnWidthDblClickResize(FColumns.TrackIndex, P, GetShiftState) then - AutoFitColumns(True, smaUseColumnOption, FColumns[FColumns.TrackIndex].Position, - FColumns[FColumns.TrackIndex].Position); - Message.Result := 0; - Result := True; - end - else - if InHeader(P) and (Message.Msg <> WM_LBUTTONDBLCLK) then - begin - case Message.Msg of - WM_NCMBUTTONDBLCLK: - Button := mbMiddle; - WM_NCRBUTTONDBLCLK: - Button := mbRight; - else - // WM_NCLBUTTONDBLCLK - Button := mbLeft; - end; - if Button = mbLeft then - Columns.AdjustDownColumn(P); - FColumns.HandleClick(P, Button, True, True); - end; - end; - // The "hot" area of the headers horizontal splitter is partly within the client area of the the tree, so we need - // to handle WM_LBUTTONDOWN here, too. - WM_LBUTTONDOWN, - WM_NCLBUTTONDOWN: - begin - - Application.CancelHint; - - if not (csDesigning in Treeview.ComponentState) then - begin - // make sure no auto scrolling is active... - Treeview.StopTimer(ScrollTimer); - Treeview.DoStateChange([], [tsScrollPending, tsScrolling]); - // ... pending editing is cancelled (actual editing remains active) - Treeview.StopTimer(EditTimer); - Treeview.DoStateChange([], [tsEditPending]); - end; - - if Message.Msg = WM_LBUTTONDOWN then - // Coordinates are already client area based. - with TWMLButtonDown(Message) do - begin - P := Point(XPos, YPos); - // #909 - FDragStart := Treeview.ClientToScreen(p); - end - else - with TWMNCLButtonDown(Message) do - begin - // want the drag start point in screen coordinates - FDragStart := Point(XCursor, YCursor); - P := Treeview.ScreenToClient(FDragStart); - end; - - IsInHeader := InHeader(P); - // in design-time header columns are always resizable - if (csDesigning in Treeview.ComponentState) then - IsVSplitterHit := InHeaderSplitterArea(P) - else - IsVSplitterHit := InHeaderSplitterArea(P) and CanSplitterResize(P); - IsHSplitterHit := HSplitterHit; - - if IsVSplitterHit or IsHSplitterHit then - begin - FTrackStart := P; - FColumns.HoverIndex := NoColumn; - if IsVSplitterHit then - begin - if not (csDesigning in Treeview.ComponentState) then - DoBeforeHeightTracking(GetShiftState); - Include(FStates, hsHeightTrackPending); - end - else - begin - if not (csDesigning in Treeview.ComponentState) then - DoBeforeColumnWidthTracking(FColumns.TrackIndex, GetShiftState); - Include(FStates, hsColumnWidthTrackPending); - end; - - SetCapture(Treeview.Handle); - Result := True; - Message.Result := 0; - end - else - if IsInHeader then - begin - HitIndex := Columns.AdjustDownColumn(P); - // in design-time header columns are always draggable - if ((csDesigning in Treeview.ComponentState) and (HitIndex > NoColumn)) or - ((hoDrag in FOptions) and (HitIndex > NoColumn) and (coDraggable in FColumns[HitIndex].Options)) then - begin - // Show potential drag operation. - // Disabled columns do not start a drag operation because they can't be clicked. - Include(FStates, hsDragPending); - SetCapture(Treeview.Handle); - Result := True; - Message.Result := 0; - end; - end; - - // This is a good opportunity to notify the application. - if not (csDesigning in Treeview.ComponentState) and IsInHeader then - FOwner.DoHeaderMouseDown(mbLeft, GetShiftState, P.X, P.Y + Integer(FHeight)); - end; - WM_NCRBUTTONDOWN: - begin - with TWMNCRButtonDown(Message) do - P := FOwner.ScreenToClient(Point(XCursor, YCursor)); - if InHeader(P) then - FOwner.DoHeaderMouseDown(mbRight, GetShiftState, P.X, P.Y + Integer(FHeight)); - end; - WM_NCRBUTTONUP: - if not (csDesigning in FOwner.ComponentState) then - with TWMNCRButtonUp(Message) do - begin - Application.CancelHint; - P := FOwner.ScreenToClient(Point(XCursor, YCursor)); - if InHeader(P) then - begin - HandleMessage := FColumns.HandleClick(P, mbRight, True, False); - FOwner.DoHeaderMouseUp(mbRight, GetShiftState, P.X, P.Y + Integer(FHeight)); - end; - end; - // When the tree window has an active mouse capture then we only get "client-area" messages. - WM_LBUTTONUP, - WM_NCLBUTTONUP: - begin - Application.CancelHint; - - if FStates <> [] then - begin - ReleaseCapture; - if hsDragging in FStates then - begin - // successfull dragging moves columns - with TWMLButtonUp(Message) do - P := Treeview.ClientToScreen(Point(XPos, YPos)); - GetWindowRect(Treeview.Handle, R); - with FColumns do - begin - FDragImage.EndDrag; - - //Problem fixed: - //Column Header does not paint correctly after a drop in certain conditions - //** The conditions are, drag is across header, mouse is not moved after - //the drop and the graphics hardware is slow in certain operations (encountered - //on Windows 10). - //Fix for the problem on certain systems where the dropped column header - //does not appear in the new position if the mouse is not moved after - //the drop. The reason is that the restore backup image operation (BitBlt) - //in the above EndDrag is slower than the header repaint in the code below - //and overlaps the new changed header with the older image. - //This happens because BitBlt seems to operate in its own thread in the - //graphics hardware and finishes later than the following code. - // - //To solve this problem, we introduce a small delay here so that the - //changed header in the following code is correctly repainted after - //the delayed BitBlt above has finished operation to restore the old - //backup image. - sleep(50); - - if (FDropTarget > -1) and (FDropTarget <> FDragIndex) and PtInRect(R, P) then - begin - OldPosition := FColumns[FDragIndex].Position; - if FColumns.DropBefore then - begin - if FColumns[FDragIndex].Position < FColumns[FDropTarget].Position then - FColumns[FDragIndex].Position := Max(0, FColumns[FDropTarget].Position - 1) - else - FColumns[FDragIndex].Position := FColumns[FDropTarget].Position; - end - else - begin - if FColumns[FDragIndex].Position < FColumns[FDropTarget].Position then - FColumns[FDragIndex].Position := FColumns[FDropTarget].Position - else - FColumns[FDragIndex].Position := FColumns[FDropTarget].Position + 1; - end; - Treeview.DoHeaderDragged(FDragIndex, OldPosition); - end - else - Treeview.DoHeaderDraggedOut(FDragIndex, P); - FDropTarget := NoColumn; - end; - Invalidate(nil); - end; - Result := True; - Message.Result := 0; - end; - - case Message.Msg of - WM_LBUTTONUP: - with TWMLButtonUp(Message) do - begin - if FColumns.DownIndex > NoColumn then - FColumns.HandleClick(Point(XPos, YPos), mbLeft, False, False); - if FStates <> [] then - FOwner.DoHeaderMouseUp(mbLeft, KeysToShiftState(Keys), XPos, YPos); - end; - WM_NCLBUTTONUP: - with TWMNCLButtonUp(Message) do - begin - P := FOwner.ScreenToClient(Point(XCursor, YCursor)); - FColumns.HandleClick(P, mbLeft, False, False); - FOwner.DoHeaderMouseUp(mbLeft, GetShiftState, P.X, P.Y + Integer(FHeight)); - end; - end; - - if FColumns.TrackIndex > NoColumn then - begin - if hsColumnWidthTracking in FStates then - DoAfterColumnWidthTracking(FColumns.TrackIndex); - Invalidate(Columns[FColumns.TrackIndex]); - FColumns.TrackIndex := NoColumn; - end; - if FColumns.DownIndex > NoColumn then - begin - Invalidate(Columns[FColumns.DownIndex]); - FColumns.DownIndex := NoColumn; - end; - if hsHeightTracking in FStates then - DoAfterHeightTracking; - - FStates := FStates - [hsDragging, hsDragPending, - hsColumnWidthTracking, hsColumnWidthTrackPending, - hsHeightTracking, hsHeightTrackPending]; - end;// WM_NCLBUTTONUP - // hovering, mouse leave detection - WM_NCMOUSEMOVE: - with TWMNCMouseMove(Message), FColumns do - begin - P := Treeview.ScreenToClient(Point(XCursor, YCursor)); - Treeview.DoHeaderMouseMove(GetShiftState, P.X, P.Y + Integer(FHeight)); - if InHeader(P) and ((AdjustHoverColumn(P)) or ((FDownIndex >= 0) and (FHoverIndex <> FDownIndex))) then - begin - // We need a mouse leave detection from here for the non client area. - // TODO: The best solution available would be the TrackMouseEvent API. - // With the drop of the support of Win95 totally and WinNT4 we should replace the timer. - Treeview.StopTimer(HeaderTimer); - SetTimer(Treeview.Handle, HeaderTimer, 50, nil); - // use Delphi's internal hint handling for header hints too - if hoShowHint in FOptions then - begin - // client coordinates! - XCursor := P.X; - YCursor := P.Y + Integer(FHeight); - Application.HintMouseMessage(Treeview, Message); - end; - end; - end; - WM_TIMER: - if TWMTimer(Message).TimerID = HeaderTimer then - begin - // determine current mouse position to check if it left the window - GetCursorPos(P); - P := Treeview.ScreenToClient(P); - with FColumns do - begin - if not InHeader(P) or ((FDownIndex > NoColumn) and (FHoverIndex <> FDownIndex)) then - begin - Treeview.StopTimer(HeaderTimer); - FHoverIndex := NoColumn; - FClickIndex := NoColumn; - FDownIndex := NoColumn; - FCheckBoxHit := False; - Result := True; - Message.Result := 0; - Invalidate(nil); - end; - end; - end; - WM_MOUSEMOVE: // mouse capture and general message redirection - Result := HandleHeaderMouseMove(TWMMouseMove(Message)); - WM_SETCURSOR: - // Feature: design-time header - if (FStates = []) then - begin - // Retrieve last cursor position (GetMessagePos does not work here, I don't know why). - GetCursorPos(P); - - // Is the mouse in the header rectangle and near the splitters? - P := Treeview.ScreenToClient(P); - IsHSplitterHit := HSplitterHit; - // in design-time header columns are always resizable - if (csDesigning in Treeview.ComponentState) then - IsVSplitterHit := InHeaderSplitterArea(P) - else - IsVSplitterHit := InHeaderSplitterArea(P) and CanSplitterResize(P); - - if IsVSplitterHit or IsHSplitterHit then - begin - NewCursor := Screen.Cursors[Treeview.Cursor]; - if IsVSplitterHit and ((hoHeightResize in FOptions) or (csDesigning in Treeview.ComponentState)) then - NewCursor := Screen.Cursors[crVertSplit] - else - if IsHSplitterHit then - NewCursor := Screen.Cursors[crHeaderSplit]; - - if not (csDesigning in Treeview.ComponentState) then - Treeview.DoGetHeaderCursor(NewCursor); - Result := NewCursor <> Screen.Cursors[crDefault]; - if Result then - begin - Winapi.Windows.SetCursor(NewCursor); - Message.Result := 1; - end; - end; - end - else - begin - Message.Result := 1; - Result := True; - end; - WM_KEYDOWN, - WM_KILLFOCUS: - if (Message.Msg = WM_KILLFOCUS) or - (TWMKeyDown(Message).CharCode = VK_ESCAPE) then - begin - if hsDragging in FStates then - begin - ReleaseCapture; - FDragImage.EndDrag; - Exclude(FStates, hsDragging); - FColumns.DropTarget := NoColumn; - Invalidate(nil); - Result := True; - Message.Result := 0; - end - else - begin - if [hsColumnWidthTracking, hsHeightTracking] * FStates <> [] then - begin - ReleaseCapture; - if hsColumnWidthTracking in FStates then - DoAfterColumnWidthTracking(FColumns.TrackIndex); - if hsHeightTracking in FStates then - DoAfterHeightTracking; - Result := True; - Message.Result := 0; - end; - - FStates := FStates - [hsColumnWidthTracking, hsColumnWidthTrackPending, - hsHeightTracking, hsHeightTrackPending]; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.ImageListChange(Sender: TObject); - -begin - if not (csDestroying in Treeview.ComponentState) then - Invalidate(nil); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.PrepareDrag(P, Start: TPoint); - -// Initializes dragging of the header, P is the current mouse postion and Start the initial mouse position. - -var - Image: TBitmap; - ImagePos: TPoint; - DragColumn: TVirtualTreeColumn; - RTLOffset: Integer; - -begin - // Determine initial position of drag image (screen coordinates). - FColumns.DropTarget := NoColumn; - Start := Treeview.ScreenToClient(Start); - Inc(Start.Y, FHeight); - FColumns.DragIndex := FColumns.ColumnFromPosition(Start); - DragColumn := FColumns[FColumns.DragIndex]; - - Image := TBitmap.Create; - with Image do - try - PixelFormat := pf32Bit; - SetSize(DragColumn.Width, FHeight); - - // Erase the entire image with the color key value, for the case not everything - // in the image is covered by the header image. - Canvas.Brush.Color := clBtnFace; - Canvas.FillRect(Rect(0, 0, Width, Height)); - - if TreeView.UseRightToLeftAlignment then - RTLOffset := Treeview.ComputeRTLOffset - else - RTLOffset := 0; - with DragColumn do - FColumns.PaintHeader(Canvas, Rect(FLeft, 0, FLeft + Width, Height), Point(-RTLOffset, 0), RTLOffset); - - if Treeview.UseRightToLeftAlignment then - ImagePos := Treeview.ClientToScreen(Point(DragColumn.Left + Treeview.ComputeRTLOffset(True), 0)) - else - ImagePos := Treeview.ClientToScreen(Point(DragColumn.Left, 0)); - // Column rectangles are given in local window coordinates not client coordinates. - Dec(ImagePos.Y, FHeight); - - if hoRestrictDrag in FOptions then - FDragImage.MoveRestriction := dmrHorizontalOnly - else - FDragImage.MoveRestriction := dmrNone; - FDragImage.PrepareDrag(Image, ImagePos, P, nil); - FDragImage.ShowDragImage; - finally - Image.Free; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.ReadColumns(Reader: TReader); - -begin - Include(FStates, hsLoading); - Columns.Clear; - Reader.ReadValue; - Reader.ReadCollection(Columns); - Exclude(FStates, hsLoading); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.RecalculateHeader; - -// Initiate a recalculation of the non-client area of the owner tree. - -begin - if Treeview.HandleAllocated then - begin - Treeview.UpdateHeaderRect; - SetWindowPos(Treeview.Handle, 0, 0, 0, 0, 0, SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOACTIVATE or SWP_NOOWNERZORDER or - SWP_NOSENDCHANGING or SWP_NOSIZE or SWP_NOZORDER); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.RescaleHeader; - -// Rescale the fixed elements (fixed columns, header itself) to FixedAreaConstraints. - -var - FixedWidth, - MaxFixedWidth, - MinFixedWidth: Integer; - - //--------------- local function -------------------------------------------- - - procedure ComputeConstraints; - - var - I: TColumnIndex; - - begin - with FColumns do - begin - I := GetFirstVisibleColumn; - while I > NoColumn do - begin - if (coFixed in FColumns[I].Options) and (FColumns[I].Width < FColumns[I].MinWidth) then - FColumns[I].InternalSetWidth(FColumns[I].MinWidth); //SetWidth has side effects and this bypasses them - I := GetNextVisibleColumn(I); - end; - FixedWidth := GetVisibleFixedWidth; - end; - - with FFixedAreaConstraints do - begin - MinFixedWidth := (TreeView.ClientWidth * FMinWidthPercent) div 100; - MaxFixedWidth := (TreeView.ClientWidth * FMaxWidthPercent) div 100; - end; - end; - - //----------- end local function -------------------------------------------- - -begin - if ([csLoading, csReading, csWriting, csDestroying] * Treeview.ComponentState = []) and not - (hsLoading in FStates) and Treeview.HandleAllocated then - begin - Include(FStates, hsScaling); - - SetHeight(FHeight); - RecalculateHeader; - - with FFixedAreaConstraints do - if (FMaxWidthPercent > 0) or (FMinWidthPercent > 0) or (FMinHeightPercent > 0) or (FMaxHeightPercent > 0) then - begin - ComputeConstraints; - - with FColumns do - if (FMaxWidthPercent > 0) and (FixedWidth > MaxFixedWidth) then - ResizeColumns(MaxFixedWidth - FixedWidth, 0, Count - 1, [coVisible, coFixed]) - else - if (FMinWidthPercent > 0) and (FixedWidth < MinFixedWidth) then - ResizeColumns(MinFixedWidth - FixedWidth, 0, Count - 1, [coVisible, coFixed]); - - FColumns.UpdatePositions; - end; - - Exclude(FStates, hsScaling); - Exclude(FStates, hsNeedScaling); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.UpdateMainColumn(); - -// Called once the load process of the owner tree is done. - -begin - if FMainColumn < 0 then - MainColumn := 0; - if FMainColumn > FColumns.Count - 1 then - MainColumn := FColumns.Count - 1; - if (FMainColumn >= 0) and not (coVisible in Self.Columns[FMainColumn].Options) then - begin - // Issue #946: Choose new MainColumn if current one ist not visible - MainColumn := Self.Columns.GetFirstVisibleColumn(); - end -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.UpdateSpringColumns; - -var - I: TColumnIndex; - SpringCount: Integer; - Sign: Integer; - ChangeBy: Single; - Difference: Single; - NewAccumulator: Single; - -begin - with TreeView do - ChangeBy := FHeaderRect.Right - FHeaderRect.Left - FLastWidth; - if (hoAutoSpring in FOptions) and (FLastWidth <> 0) and (ChangeBy <> 0) then - begin - // Stay positive if downsizing the control. - if ChangeBy < 0 then - Sign := -1 - else - Sign := 1; - ChangeBy := Abs(ChangeBy); - // Count how many columns have spring enabled. - SpringCount := 0; - for I := 0 to FColumns.Count-1 do - if [coVisible, coAutoSpring] * FColumns[I].Options = [coVisible, coAutoSpring] then - Inc(SpringCount); - if SpringCount > 0 then - begin - // Calculate the size to add/sub to each columns. - Difference := ChangeBy / SpringCount; - // Adjust the column's size accumulators and resize if the result is >= 1. - for I := 0 to FColumns.Count - 1 do - if [coVisible, coAutoSpring] * FColumns[I].Options = [coVisible, coAutoSpring] then - begin - // Sum up rest changes from previous runs and the amount from this one and store it in the - // column. If there is at least one pixel difference then do a resize and reset the accumulator. - NewAccumulator := FColumns[I].SpringRest + Difference; - // Set new width if at least one pixel size difference is reached. - if NewAccumulator >= 1 then - FColumns[I].SetWidth(FColumns[I].Width + (Trunc(NewAccumulator) * Sign)); - FColumns[I].SpringRest := Frac(NewAccumulator); - - // Keep track of the size count. - ChangeBy := ChangeBy - Difference; - // Exit loop if resize count drops below freezing point. - if ChangeBy < 0 then - Break; - end; - end; - end; - with TreeView do - FLastWidth := FHeaderRect.Right - FHeaderRect.Left; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -type - // --- HACK WARNING! - // This type cast is a partial rewrite of the private section of TWriter. The purpose is to have access to - // the FPropPath member, which is otherwise not accessible. The reason why this access is needed is that - // with nested components this member contains unneeded property path information. These information prevent - // successful load of the stored properties later. - // In System.Classes.pas you can see that FPropPath is reset several times to '' to prevent this case for certain properies. - // Unfortunately, there is no clean way for us here to do the same. - {$hints off} - TWriterHack = class(TFiler) - private - FRootAncestor: TComponent; - FPropPath: string; - end; - {$hints on} - -procedure TVTHeader.WriteColumns(Writer: TWriter); - -// Write out the columns but take care for the case VT is a nested component. - -var - LastPropPath: string; - -begin - // Save last property path for restoration. - LastPropPath := TWriterHack(Writer).FPropPath; - try - // If VT is a nested component then this path contains the name of the parent component at this time - // (otherwise it is already empty). This path is then combined with the property name under which the tree - // is defined in the parent component. Unfortunately, the load code in System.Classes.pas does not consider this case - // is then unable to load this property. - TWriterHack(Writer).FPropPath := ''; - Writer.WriteCollection(Columns); - finally - TWriterHack(Writer).FPropPath := LastPropPath; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.AllowFocus(ColumnIndex: TColumnIndex): Boolean; -begin - Result := False; - if not FColumns.IsValidColumn(ColumnIndex) then - Exit; // Just in case. - - Result := (coAllowFocus in FColumns[ColumnIndex].Options); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.AreColumnsStored: Boolean; -begin - // The columns are stored by the owner tree to support Visual Form Inheritance - // GnutGetText skips non-stored properties, so retur Stored True at runtime - Result := not (csDesigning in Self.Treeview.ComponentState); -end; - -procedure TVTHeader.Assign(Source: TPersistent); - -begin - if Source is TVTHeader then - begin - AutoSizeIndex := TVTHeader(Source).AutoSizeIndex; - Background := TVTHeader(Source).Background; - Columns := TVTHeader(Source).Columns; - Font := TVTHeader(Source).Font; - FixedAreaConstraints.Assign(TVTHeader(Source).FixedAreaConstraints); - Height := TVTHeader(Source).Height; - Images := TVTHeader(Source).Images; - MainColumn := TVTHeader(Source).MainColumn; - Options := TVTHeader(Source).Options; - ParentFont := TVTHeader(Source).ParentFont; - PopupMenu := TVTHeader(Source).PopupMenu; - SortColumn := TVTHeader(Source).SortColumn; - SortDirection := TVTHeader(Source).SortDirection; - Style := TVTHeader(Source).Style; - - RescaleHeader; - end - else - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.AutoFitColumns(Animated: Boolean = True; SmartAutoFitType: TSmartAutoFitType = smaUseColumnOption; - RangeStartCol: Integer = NoColumn; RangeEndCol: Integer = NoColumn); - - //--------------- local functions ------------------------------------------- - - function GetUseSmartColumnWidth(ColumnIndex: TColumnIndex): Boolean; - - begin - case SmartAutoFitType of - smaAllColumns: - Result := True; - smaUseColumnOption: - Result := coSmartResize in FColumns.Items[ColumnIndex].Options; - else - Result := False; - end; - end; - - //---------------------------------------------------------------------------- - - procedure DoAutoFitColumn(Column: TColumnIndex); - - begin - with FColumns do - if ([coResizable, coVisible] * Items[FPositionToIndex[Column]].Options = [coResizable, coVisible]) and - DoBeforeAutoFitColumn(FPositionToIndex[Column], SmartAutoFitType) and not TreeView.OperationCanceled then - begin - if Animated then - AnimatedResize(FPositionToIndex[Column], Treeview.GetMaxColumnWidth(FPositionToIndex[Column], - GetUseSmartColumnWidth(FPositionToIndex[Column]))) - else - FColumns[FPositionToIndex[Column]].Width := Treeview.GetMaxColumnWidth(FPositionToIndex[Column], - GetUseSmartColumnWidth(FPositionToIndex[Column])); - - DoAfterAutoFitColumn(FPositionToIndex[Column]); - end; - end; - - //--------------- end local functions ---------------------------------------- - -var - I: Integer; - StartCol, - EndCol: Integer; - -begin - StartCol := Max(NoColumn + 1, RangeStartCol); - - if RangeEndCol <= NoColumn then - EndCol := FColumns.Count - 1 - else - EndCol := Min(RangeEndCol, FColumns.Count - 1); - - if StartCol > EndCol then - Exit; // nothing to do - - TreeView.StartOperation(okAutoFitColumns); - FDoingAutoFitColumns := true; - try - if Assigned(TreeView.FOnBeforeAutoFitColumns) then - TreeView.FOnBeforeAutoFitColumns(Self, SmartAutoFitType); - - for I := StartCol to EndCol do - DoAutoFitColumn(I); - - if Assigned(TreeView.FOnAfterAutoFitColumns) then - TreeView.FOnAfterAutoFitColumns(Self); - - finally - Treeview.EndOperation(okAutoFitColumns); - TreeView.Invalidate(); - FDoingAutoFitColumns := false; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.InHeader(P: TPoint): Boolean; - -// Determines whether the given point (client coordinates!) is within the header rectangle (non-client coordinates). - -var - R, RW: TRect; - -begin - R := Treeview.FHeaderRect; - - // Current position of the owner in screen coordinates. - GetWindowRect(Treeview.Handle, RW); - - // Convert to client coordinates. - MapWindowPoints(0, Treeview.Handle, RW, 2); - - // Consider the header within this rectangle. - OffsetRect(R, RW.Left, RW.Top); - Result := PtInRect(R, P); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.InHeaderSplitterArea(P: TPoint): Boolean; - -// Determines whether the given point (client coordinates!) hits the horizontal splitter area of the header. - -var - R, RW: TRect; - -begin - if (P.Y > 2) or (P.Y < -2) or not (hoVisible in FOptions) then - Result := False - else - begin - R := Treeview.FHeaderRect; - Inc(R.Bottom, 2); - - // Current position of the owner in screen coordinates. - GetWindowRect(Treeview.Handle, RW); - - // Convert to client coordinates. - MapWindowPoints(0, Treeview.Handle, RW, 2); - - // Consider the header within this rectangle. - OffsetRect(R, RW.Left, RW.Top); - Result := PtInRect(R, P); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.Invalidate(Column: TVirtualTreeColumn; ExpandToBorder: Boolean = False; UpdateNowFlag : Boolean = False); - -// Because the header is in the non-client area of the tree it needs some special handling in order to initiate its -// repainting. -// If ExpandToBorder is True then not only the given column but everything or (depending on hoFullRepaintOnResize) just -// everything to its right (or left, in RTL mode) will be invalidated (useful for resizing). This makes only sense when -// a column is given. - -var - R, RW: TRect; - Flags: Cardinal; - -begin - if (hoVisible in FOptions) and Treeview.HandleAllocated then - with Treeview do - begin - if Column = nil then - R := FHeaderRect - else - begin - R := Column.GetRect; - if not (coFixed in Column.Options) then - OffsetRect(R, -FEffectiveOffsetX, 0); - if UseRightToLeftAlignment then - OffsetRect(R, ComputeRTLOffset, 0); - if ExpandToBorder then - begin - if (hoFullRepaintOnResize in FHeader.Options) then - begin - R.Left := FHeaderRect.Left; - R.Right := FHeaderRect.Right; - end - else - begin - if UseRightToLeftAlignment then - R.Left := FHeaderRect.Left - else - R.Right := FHeaderRect.Right; - end; - end; - end; - R.Bottom := Treeview.ClientHeight; // We want to repaint the entire column to bottom, not just the header - - // Current position of the owner in screen coordinates. - GetWindowRect(Handle, RW); - - // Consider the header within this rectangle. - OffsetRect(R, RW.Left, RW.Top); - - // Expressed in client coordinates (because RedrawWindow wants them so, they will actually become negative). - MapWindowPoints(0, Handle, R, 2); - Flags := RDW_FRAME or RDW_INVALIDATE or RDW_VALIDATE or RDW_NOINTERNALPAINT or RDW_NOERASE or RDW_NOCHILDREN; - if UpdateNowFlag then - Flags := Flags or RDW_UPDATENOW; - RedrawWindow(Handle, @R, 0, Flags); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.LoadFromStream(const Stream: TStream); - -// restore the state of the header from the given stream - -var - Dummy, - Version: Integer; - S: AnsiString; - OldOptions: TVTHeaderOptions; - -begin - Include(FStates, hsLoading); - with Stream do - try - // Switch off all options which could influence loading the columns (they will be later set again). - OldOptions := FOptions; - FOptions := []; - - // Determine whether the stream contains data without a version number. - ReadBuffer(Dummy, SizeOf(Dummy)); - if Dummy > -1 then - begin - // Seek back to undo the read operation if this is an old stream format. - Seek(-SizeOf(Dummy), soFromCurrent); - Version := -1; - end - else // Read version number if this is a "versionized" format. - ReadBuffer(Version, SizeOf(Version)); - Columns.LoadFromStream(Stream, Version); - - ReadBuffer(Dummy, SizeOf(Dummy)); - AutoSizeIndex := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - Background := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - Height := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - FOptions := OldOptions; - Options := TVTHeaderOptions(Dummy); - // PopupMenu is neither saved nor restored - ReadBuffer(Dummy, SizeOf(Dummy)); - Style := TVTHeaderStyle(Dummy); - // TFont has no own save routine so we do it manually - with Font do - begin - ReadBuffer(Dummy, SizeOf(Dummy)); - Color := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - Height := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - SetLength(S, Dummy); - ReadBuffer(PAnsiChar(S)^, Dummy); - Name := UTF8ToString(S); - ReadBuffer(Dummy, SizeOf(Dummy)); - Pitch := TFontPitch(Dummy); - ReadBuffer(Dummy, SizeOf(Dummy)); - Style := TFontStyles(Byte(Dummy)); - end; - - // Read data introduced by stream version 1+. - if Version > 0 then - begin - ReadBuffer(Dummy, SizeOf(Dummy)); - MainColumn := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - SortColumn := Dummy; - ReadBuffer(Dummy, SizeOf(Dummy)); - SortDirection := TSortDirection(Byte(Dummy)); - end; - - // Read data introduced by stream version 5+. - if Version > 4 then - begin - ReadBuffer(Dummy, SizeOf(Dummy)); - ParentFont := Boolean(Dummy); - ReadBuffer(Dummy, SizeOf(Dummy)); - FMaxHeight := Integer(Dummy); - ReadBuffer(Dummy, SizeOf(Dummy)); - FMinHeight := Integer(Dummy); - ReadBuffer(Dummy, SizeOf(Dummy)); - FDefaultHeight := Integer(Dummy); - with FFixedAreaConstraints do - begin - ReadBuffer(Dummy, SizeOf(Dummy)); - FMaxHeightPercent := TVTConstraintPercent(Dummy); - ReadBuffer(Dummy, SizeOf(Dummy)); - FMaxWidthPercent := TVTConstraintPercent(Dummy); - ReadBuffer(Dummy, SizeOf(Dummy)); - FMinHeightPercent := TVTConstraintPercent(Dummy); - ReadBuffer(Dummy, SizeOf(Dummy)); - FMinWidthPercent := TVTConstraintPercent(Dummy); - end; - end; - finally - Exclude(FStates, hsLoading); - RecalculateHeader(); - Treeview.DoColumnResize(NoColumn); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTHeader.ResizeColumns(ChangeBy: Integer; RangeStartCol: TColumnIndex; RangeEndCol: TColumnIndex; - Options: TVTColumnOptions = [coVisible]): Integer; - -// Distribute the given width change to a range of columns. A 'fair' way is used to distribute ChangeBy to the columns, -// while ensuring that everything that can be distributed will be distributed. - -var - Start, - I: TColumnIndex; - ColCount, - ToGo, - Sign, - Rest, - MaxDelta, - Difference: Integer; - Constraints, - Widths: array of Integer; - BonusPixel: Boolean; - - //--------------- local functions ------------------------------------------- - - function IsResizable (Column: TColumnIndex): Boolean; - - begin - if BonusPixel then - Result := Widths[Column - RangeStartCol] < Constraints[Column - RangeStartCol] - else - Result := Widths[Column - RangeStartCol] > Constraints[Column - RangeStartCol]; - end; - - //--------------------------------------------------------------------------- - - procedure IncDelta(Column: TColumnIndex); - - begin - if BonusPixel then - Inc(MaxDelta, FColumns[Column].MaxWidth - Widths[Column - RangeStartCol]) - else - Inc(MaxDelta, Widths[Column - RangeStartCol] - Constraints[Column - RangeStartCol]); - end; - - //--------------------------------------------------------------------------- - - function ChangeWidth(Column: TColumnIndex; Delta: Integer): Integer; - - begin - if Delta > 0 then - Delta := Min(Delta, Constraints[Column - RangeStartCol] - Widths[Column - RangeStartCol]) - else - Delta := Max(Delta, Constraints[Column - RangeStartCol] - Widths[Column - RangeStartCol]); - - Inc(Widths[Column - RangeStartCol], Delta); - Dec(ToGo, Abs(Delta)); - Result := Abs(Delta); - end; - - //--------------------------------------------------------------------------- - - function ReduceConstraints: Boolean; - - var - MaxWidth, - MaxReserveCol, - Column: TColumnIndex; - - begin - Result := True; - if not (hsScaling in FStates) or BonusPixel then - Exit; - - MaxWidth := 0; - MaxReserveCol := NoColumn; - for Column := RangeStartCol to RangeEndCol do - if (Options * FColumns[Column].Options = Options) and - (FColumns[Column].Width > MaxWidth) then - begin - MaxWidth := Widths[Column - RangeStartCol]; - MaxReserveCol := Column; - end; - - if (MaxReserveCol <= NoColumn) or (Constraints[MaxReserveCol - RangeStartCol] <= 10) then - Result := False - else - Dec(Constraints[MaxReserveCol - RangeStartCol], - Constraints[MaxReserveCol - RangeStartCol] div 10); - end; - - //----------- end local functions ------------------------------------------- - -begin - Result := 0; - if (ChangeBy <> 0) and (RangeEndCol >= 0) then // RangeEndCol == -1 means no columns, so nothing to do - begin - // Do some initialization here - BonusPixel := ChangeBy > 0; - Sign := IfThen(BonusPixel, 1, -1); - Start := IfThen(BonusPixel, RangeStartCol, RangeEndCol); - ToGo := Abs(ChangeBy); - SetLength(Widths, RangeEndCol - RangeStartCol + 1); - SetLength(Constraints, RangeEndCol - RangeStartCol + 1); - for I := RangeStartCol to RangeEndCol do - begin - Widths[I - RangeStartCol] := FColumns[I].Width; - Constraints[I - RangeStartCol] := IfThen(BonusPixel, FColumns[I].MaxWidth, FColumns[I].MinWidth); - end; - - repeat - repeat - MaxDelta := 0; - ColCount := 0; - for I := RangeStartCol to RangeEndCol do - if (Options * FColumns[I].Options = Options) and IsResizable(I) then - begin - Inc(ColCount); - IncDelta(I); - end; - if MaxDelta < Abs(ChangeBy) then - if not ReduceConstraints then - Break; - until (MaxDelta >= Abs(ChangeBy)) or not (hsScaling in FStates); - - if ColCount = 0 then - Break; - - ToGo := Min(ToGo, MaxDelta); - Difference := ToGo div ColCount; - Rest := ToGo mod ColCount; - - if Difference > 0 then - for I := RangeStartCol to RangeEndCol do - if (Options * FColumns[I].Options = Options) and IsResizable(I) then - ChangeWidth(I, Difference * Sign); - - // Now distribute Rest. - I := Start; - while Rest > 0 do - begin - if (Options * FColumns[I].Options = Options) and IsResizable(I) then - if FColumns[I].BonusPixel <> BonusPixel then - begin - Dec(Rest, ChangeWidth(I, Sign)); - FColumns[I].BonusPixel := BonusPixel; - end; - Inc(I, Sign); - if (BonusPixel and (I > RangeEndCol)) or (not BonusPixel and (I < RangeStartCol)) then - begin - for I := RangeStartCol to RangeEndCol do - if Options * FColumns[I].Options = Options then - FColumns[I].BonusPixel := not FColumns[I].BonusPixel; - I := Start; - end; - end; - until ToGo <= 0; - - // Now set the computed widths. We also compute the result here. - Include(FStates, hsResizing); - for I := RangeStartCol to RangeEndCol do - if (Options * FColumns[I].Options = Options) then - begin - Inc(Result, Widths[I - RangeStartCol] - FColumns[I].Width); - FColumns[I].SetWidth(Widths[I - RangeStartCol]); - end; - Exclude(FStates, hsResizing); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.RestoreColumns; - -// Restores all columns to their width which they had before they have been auto fitted. - -var - I: TColumnIndex; - -begin - with FColumns do - for I := Count - 1 downto 0 do - if [coResizable, coVisible] * Items[FPositionToIndex[I]].Options = [coResizable, coVisible] then - Items[I].RestoreLastWidth; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.ToggleSortDirection; - -// Toggles the current sorting direction - -begin - if SortDirection = sdDescending then - SortDirection := sdAscending - else - SortDirection := sdDescending; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTHeader.SaveToStream(const Stream: TStream); - -// Saves the complete state of the header into the provided stream. - -var - Dummy: Integer; - Tmp: AnsiString; - -begin - with Stream do - begin - // In previous version of VT was no header stream version defined. - // For feature enhancements it is necessary, however, to know which stream - // format we are trying to load. - // In order to distict from non-version streams an indicator is inserted. - Dummy := -1; - WriteBuffer(Dummy, SizeOf(Dummy)); - // Write current stream version number, nothing more is required at the time being. - Dummy := VTHeaderStreamVersion; - WriteBuffer(Dummy, SizeOf(Dummy)); - - // Save columns in case they depend on certain options (like auto size). - Columns.SaveToStream(Stream); - - Dummy := FAutoSizeIndex; - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := FBackgroundColor; - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := FHeight; - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Integer(FOptions); - WriteBuffer(Dummy, SizeOf(Dummy)); - // PopupMenu is neither saved nor restored - Dummy := Ord(FStyle); - WriteBuffer(Dummy, SizeOf(Dummy)); - // TFont has no own save routine so we do it manually - with Font do - begin - Dummy := Color; - WriteBuffer(Dummy, SizeOf(Dummy)); - - // Need only to write one: size or height, I decided to write height. - Dummy := Height; - WriteBuffer(Dummy, SizeOf(Dummy)); - Tmp := UTF8Encode(Name); - Dummy := Length(Tmp); - WriteBuffer(Dummy, SizeOf(Dummy)); - WriteBuffer(PAnsiChar(Tmp)^, Dummy); - Dummy := Ord(Pitch); - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Byte(Style); - WriteBuffer(Dummy, SizeOf(Dummy)); - end; - - // Data introduced by stream version 1. - Dummy := FMainColumn; - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := FSortColumn; - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Byte(FSortDirection); - WriteBuffer(Dummy, SizeOf(Dummy)); - - // Data introduced by stream version 5. - Dummy := Integer(ParentFont); - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Integer(FMaxHeight); - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Integer(FMinHeight); - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Integer(FDefaultHeight); - WriteBuffer(Dummy, SizeOf(Dummy)); - with FFixedAreaConstraints do - begin - Dummy := Integer(FMaxHeightPercent); - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Integer(FMaxWidthPercent); - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Integer(FMinHeightPercent); - WriteBuffer(Dummy, SizeOf(Dummy)); - Dummy := Integer(FMinWidthPercent); - WriteBuffer(Dummy, SizeOf(Dummy)); - end; - end; -end; - -//----------------- TScrollBarOptions ---------------------------------------------------------------------------------- - -constructor TScrollBarOptions.Create(AOwner: TBaseVirtualTree); - -begin - inherited Create; - - FOwner := AOwner; - FAlwaysVisible := False; - FScrollBarStyle := sbmRegular; - FScrollBars := ssBoth; - FIncrementX := 20; - FIncrementY := 20; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TScrollBarOptions.SetAlwaysVisible(Value: Boolean); - -begin - if FAlwaysVisible <> Value then - begin - FAlwaysVisible := Value; - if not (csLoading in FOwner.ComponentState) and FOwner.HandleAllocated then - FOwner.RecreateWnd; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TScrollBarOptions.SetScrollBars(Value: TScrollStyle); - -begin - if FScrollBars <> Value then - begin - FScrollBars := Value; - if not (csLoading in FOwner.ComponentState) and FOwner.HandleAllocated then - FOwner.RecreateWnd; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TScrollBarOptions.SetScrollBarStyle(Value: TScrollBarStyle); - -begin - if FScrollBarStyle <> Value then - begin - FScrollBarStyle := Value; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TScrollBarOptions.GetOwner: TPersistent; - -begin - Result := FOwner; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TScrollBarOptions.Assign(Source: TPersistent); - -begin - if Source is TScrollBarOptions then - begin - AlwaysVisible := TScrollBarOptions(Source).AlwaysVisible; - HorizontalIncrement := TScrollBarOptions(Source).HorizontalIncrement; - ScrollBars := TScrollBarOptions(Source).ScrollBars; - ScrollBarStyle := TScrollBarOptions(Source).ScrollBarStyle; - VerticalIncrement := TScrollBarOptions(Source).VerticalIncrement; - end - else - inherited; -end; - -//----------------- TVTColors ------------------------------------------------------------------------------------------ - -constructor TVTColors.Create(AOwner: TBaseVirtualTree); -var - CE : TVTColorEnum; -begin - FOwner := AOwner; - for CE := Low(TVTColorEnum) to High(TVTColorEnum) do - FColors[CE] := cDefaultColors[CE]; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTColors.GetBackgroundColor: TColor; -begin -// XE2 VCL Style - if FOwner.VclStyleEnabled and (seClient in FOwner.StyleElements) then - Result := StyleServices.GetStyleColor(scTreeView) - else - Result := FOwner.Color; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTColors.GetColor(const Index: TVTColorEnum): TColor; -begin - // Only try to fetch the color via StyleServices if theses are enabled - // Return default/user defined color otherwise - if not (csDesigning in FOwner.ComponentState) { see issue #1185 } and FOwner.VclStyleEnabled then - begin - // If the ElementDetails are not defined, fall back to the SystemColor - case Index of - cDisabledColor: - if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemDisabled), ecTextColor, Result) then - Result := StyleServices.GetSystemColor(FColors[Index]); - cTreeLineColor: - if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttBranch), ecBorderColor, Result) then - Result := StyleServices.GetSystemColor(FColors[Index]); - cBorderColor: - if (seBorder in FOwner.StyleElements) then - Result := StyleServices.GetSystemColor(FColors[Index]) - else - Result := FColors[Index]; - cHotColor: - if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemHot), ecTextColor, Result) then - Result := StyleServices.GetSystemColor(FColors[Index]); - cHeaderHotColor: - if not StyleServices.GetElementColor(StyleServices.GetElementDetails(thHeaderItemHot), ecTextColor, Result) then - Result := StyleServices.GetSystemColor(FColors[Index]); - cSelectionTextColor: - if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemSelected), ecTextColor, Result) then - Result := StyleServices.GetSystemColor(clHighlightText); - cUnfocusedColor: - if not StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemSelectedNotFocus), ecTextColor, Result) then - Result := StyleServices.GetSystemColor(FColors[Index]); - else - Result := StyleServices.GetSystemColor(FColors[Index]); - end; - end - else - Result := FColors[Index]; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTColors.GetHeaderFontColor: TColor; -begin -// XE2+ VCL Style - if FOwner.VclStyleEnabled and (seFont in FOwner.StyleElements) then - StyleServices.GetElementColor(StyleServices.GetElementDetails(thHeaderItemNormal), ecTextColor, Result) - else - Result := FOwner.Header.Font.Color; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTColors.GetNodeFontColor: TColor; -begin - if FOwner.VclStyleEnabled and (seFont in FOwner.StyleElements) then - StyleServices.GetElementColor(StyleServices.GetElementDetails(ttItemNormal), ecTextColor, Result) - else - Result := FOwner.Font.Color; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTColors.GetSelectedNodeFontColor(Focused: boolean): TColor; -begin - if Focused then begin - if (tsUseExplorerTheme in FOwner.TreeStates) and not IsHighContrastEnabled then begin - Result := NodeFontColor - end - else - Result := SelectionTextColor - end// if Focused - else - Result := UnfocusedColor; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTColors.SetColor(const Index: TVTColorEnum; const Value: TColor); - -begin - if FColors[Index] <> Value then - begin - FColors[Index] := Value; - if not (csLoading in FOwner.ComponentState) and FOwner.HandleAllocated then - begin - // Cause helper bitmap rebuild if the button color changed. - case Index of - cTreeLineColor: - begin - FOwner.PrepareBitmaps(True, False); - FOwner.Invalidate; - end; - cBorderColor: - RedrawWindow(FOwner.Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN) - else - if not (tsPainting in FOwner.TreeStates) then - FOwner.Invalidate; - end;//case - end;// if - end; -end; - -function TVTColors.StyleServices(AControl: TControl): TCustomStyleServices; -begin - if AControl = nil then - AControl := fOwner; - Result := VTStyleServices(AControl); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTColors.Assign(Source: TPersistent); - -begin - if Source is TVTColors then - begin - FColors := TVTColors(Source).FColors; - if FOwner.UpdateCount = 0 then - FOwner.Invalidate; - end - else - inherited; -end; - -//----------------- TClipboardFormats ---------------------------------------------------------------------------------- - -constructor TClipboardFormats.Create(AOwner: TBaseVirtualTree); - -begin - FOwner := AOwner; - Sorted := True; - Duplicates := dupIgnore; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TClipboardFormats.Add(const S: string): Integer; - -// Restrict additions to the clipbard formats to only those which are registered with the owner tree or one of its -// ancestors. - -var - Format: Word; - RegisteredClass: TVirtualTreeClass; - -begin - RegisteredClass := TClipboardFormatList.FindFormat(S, Format); - if Assigned(RegisteredClass) and FOwner.ClassType.InheritsFrom(RegisteredClass) then - Result := inherited Add(S) - else - Result := -1; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TClipboardFormats.Insert(Index: Integer; const S: string); - -// Restrict additions to the clipbard formats to only those which are registered with the owner tree or one of its -// ancestors. - -var - Format: Word; - RegisteredClass: TVirtualTreeClass; - -begin - RegisteredClass := TClipboardFormatList.FindFormat(S, Format); - if Assigned(RegisteredClass) and FOwner.ClassType.InheritsFrom(RegisteredClass) then - inherited Insert(Index, S); -end; - -//----------------- TBaseVirtualTree ----------------------------------------------------------------------------------- - -constructor TBaseVirtualTree.Create(AOwner: TComponent); - -begin - InitializeGlobalStructures(); - - inherited; - - ControlStyle := ControlStyle - [csSetCaption] + [csCaptureMouse, csOpaque, csReplicatable, csDisplayDragImage, - csReflector]; - FTotalInternalDataSize := 0; - FNodeDataSize := -1; - Width := 200; - Height := 100; - TabStop := True; - ParentColor := False; - FDefaultNodeHeight := 18; - FDragOperations := [doCopy, doMove]; - FHotCursor := crDefault; - FScrollBarOptions := TScrollBarOptions.Create(Self); - FFocusedColumn := NoColumn; - FDragImageKind := diComplete; - FLastSelectionLevel := -1; - FSelectionBlendFactor := 128; - - FIndent := 18; - - FPlusBM := TBitmap.Create; - FHotPlusBM := TBitmap.Create; - FMinusBM := TBitmap.Create; - FHotMinusBM := TBitmap.Create; - FSelectedHotPlusBM := TBitmap.Create; - FSelectedHotMinusBM := TBitmap.Create; - - FBorderStyle := bsSingle; - FButtonStyle := bsRectangle; - FButtonFillMode := fmTreeColor; - - FHeader := GetHeaderClass.Create(Self); - - // we have an own double buffer handling - inherited DoubleBuffered := False; - - FCheckImageKind := ckSystemDefault; - - FImageChangeLink := TChangeLink.Create; - FImageChangeLink.OnChange := ImageListChange; - FStateChangeLink := TChangeLink.Create; - FStateChangeLink.OnChange := ImageListChange; - FCustomCheckChangeLink := TChangeLink.Create; - FCustomCheckChangeLink.OnChange := ImageListChange; - - FAutoExpandDelay := 1000; - FAutoScrollDelay := 1000; - FAutoScrollInterval := 1; - - FBackground := TPicture.Create; - // Similar to the Transparent property of TImage, - // this flag is Off by default. - FBackGroundImageTransparent := False; - - FDefaultPasteMode := amAddChildLast; - FMargin := 4; - FTextMargin := 4; - FImagesMargin := 2; - FLastDragEffect := DROPEFFECT_NONE; - FDragType := dtOLE; - FDragHeight := 350; - FDragWidth := 200; - - FColors := TVTColors.Create(Self); - FEditDelay := 1000; - - FDragImage := TVTDragImage.Create(Self); - with FDragImage do - begin - Fade := True; - PreBlendBias := 0; - Transparency := 200; - end; - - FAnimationDuration := 200; - FSearchTimeout := 1000; - FSearchStart := ssFocusedNode; - FNodeAlignment := naProportional; - FLineStyle := lsDotted; - FIncrementalSearch := isNone; - FClipboardFormats := TClipboardFormats.Create(Self); - FOptions := GetOptionsClass.Create(Self); - - Touch.InteractiveGestures := [igPan, igPressAndTap]; - Touch.InteractiveGestureOptions := [igoPanInertia, - igoPanSingleFingerHorizontal, igoPanSingleFingerVertical, - igoPanGutter, igoParentPassthrough]; - - if not (csDesigning in ComponentState) then //Don't create worker thread in IDE, there is no use for it - TWorkerThread.AddThreadReference(); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -destructor TBaseVirtualTree.Destroy(); -var - WasValidating: Boolean; -begin - // Disconnect all remote MSAA connections - if Assigned(FAccessibleItem) then begin - CoDisconnectObject(FAccessibleItem, 0); - FAccessibleItem := nil; - end; - if Assigned(fAccessible) then begin - CoDisconnectObject(fAccessible, 0); - fAccessible := nil; - end; - - WasValidating := (tsValidating in FStates) or (tsValidationNeeded in FStates); // Checking tsValidating is not enough, the TWorkerThread may be stuck in the first call to ChangeTreeStatesAsync() - InterruptValidation(True); - if WasValidating then - begin - // Make sure we dequeue the two synchronized calls from ChangeTreeStatesAsync(), fixes mem leak and AV reported in issue #1001, but is more a workaround. - while CheckSynchronize() do - Sleep(1); - end;// if - FOptions.InternalSetMiscOptions(FOptions.MiscOptions - [toReadOnly]); //SetMiscOptions has side effects - // Make sure there is no reference remaining to the releasing tree. - TWorkerThread.ReleaseThreadReference(); - StopWheelPanning; - CancelEditNode; - - // Just in case it didn't happen already release the edit link. - FEditLink := nil; - FClipboardFormats.Free; - // Clear will also free the drag manager if it is still alive. - Clear; - FDragImage.Free; - FColors.Free; - FBackground.Free; - - if CheckImageKind = ckSystemDefault then - FCheckImages.Free; - FScrollBarOptions.Free; - - // The window handle must be destroyed before the header is freed because it is needed in WM_NCDESTROY. - if HandleAllocated then - DestroyWindowHandle; - - // Release FDottedBrush in case WM_NCDESTROY hasn't been triggered. - if FDottedBrush <> 0 then - DeleteObject(FDottedBrush); - FDottedBrush := 0; - - FHeader.Free; - FHeader := nil; // Do not use FreeAndNil() before checking issue #497 - FreeAndNil(FOptions); // WM_NCDESTROY accesses FOptions - - FreeMem(FRoot); - - FPlusBM.Free; - FHotPlusBM.Free; - FMinusBM.Free; - FHotMinusBM.Free; - FSelectedHotPlusBM.Free; - FSelectedHotMinusBM.Free; - - // Fixes issue #1002 - Images := nil; - StateImages := nil; - CustomCheckImages := nil; - - FImageChangeLink.Free; - FStateChangeLink.Free; - FCustomCheckChangeLink.Free; - - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.AdjustTotalCount(Node: PVirtualNode; Value: Integer; Relative: Boolean = False); - -// Sets a node's total count to the given value and recursively adjusts the parent's total count -// (actually, the adjustment is done iteratively to avoid function call overheads). - -var - Difference: Integer; - Run: PVirtualNode; - -begin - if Relative then - Difference := Value - else - Difference := Value - Integer(Node.TotalCount); - if Difference <> 0 then - begin - Run := Node; - // Root node has as parent the tree view. - while Assigned(Run) and (Run <> Pointer(Self)) do - begin - Inc(Integer(Run.TotalCount), Difference); - Run := Run.Parent; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.AdjustTotalHeight(Node: PVirtualNode; Value: Integer; Relative: Boolean = False); - -// Sets a node's total height to the given value and recursively adjusts the parent's total height. - -var - Difference: Integer; - Run: PVirtualNode; - -begin - if Relative then - Difference := Value - else - Difference := Value - Integer(Node.TotalHeight); - if Difference <> 0 then - begin - Run := Node; - repeat - Inc(Integer(Run.TotalHeight), Difference); - // If the node is not visible or the parent node is not expanded or we are already at the top - // then nothing more remains to do. - if not (vsVisible in Run.States) or (Run = FRoot) or - (Run.Parent = nil) or not (vsExpanded in Run.Parent.States) then - Break; - - Run := Run.Parent; - until False; - end; - - UpdateVerticalRange; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CalculateCacheEntryCount: Integer; - -// Calculates the size of the position cache. - -begin - if FVisibleCount > 1 then - Result := Ceil(FVisibleCount / CacheThreshold) - else - Result := 0; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CalculateVerticalAlignments(var PaintInfo: TVTPaintInfo; var VButtonAlign: Integer); - -// Calculates the vertical alignment of the given node and its associated expand/collapse button during -// a node paint cycle depending on the required node alignment style. - -begin - With PaintInfo do begin - // For absolute alignment the calculation is trivial. - case FNodeAlignment of - naFromTop: - VAlign := Node.Align; - naFromBottom: - VAlign := Integer(NodeHeight[Node]) - Node.Align; - else // naProportional - // Consider button and line alignment, but make sure neither the image nor the button (whichever is taller) - // go out of the entire node height (100% means bottom alignment to the node's bounds). - if (ImageInfo[iiNormal].Index >= 0) or (ImageInfo[iiState].Index >= 0) then - begin - if (ImageInfo[iiNormal].Index >= 0) then - VAlign := ImageInfo[iiNormal].Images.Height - else - VAlign := ImageInfo[iiState].Images.Height; - VAlign := MulDiv((Integer(NodeHeight[Node]) - VAlign), Node.Align, 100) + VAlign div 2; - end - else - if toShowButtons in FOptions.PaintOptions then - VAlign := MulDiv((Integer(NodeHeight[Node]) - FPlusBM.Height), Node.Align, 100) + FPlusBM.Height div 2 - else - VAlign := MulDiv(Integer(Node.NodeHeight), Node.Align, 100); - end; - - VButtonAlign := VAlign - FPlusBM.Height div 2 - (FPlusBM.Height and 1); - end;// With PaintInfo -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ChangeCheckState(Node: PVirtualNode; Value: TCheckState): Boolean; - -// Sets the check state of the node according to the given value and the node's check type. -// If the check state must be propagated to the parent nodes and one of them refuses to change then -// nothing happens and False is returned, otherwise True. - -var - Run: PVirtualNode; - UncheckedCount, - MixedCheckCount, - CheckedCount: Cardinal; - -begin - Result := not (vsChecking in Node.States); - with Node^ do - if Result then - begin - Include(States, vsChecking); - try - if not (vsInitialized in States) then - InitNode(Node) - else if CheckState = Value then - begin - // Value didn't change and node was initialized, so nothing to do - Result := False; - Exit; - end;//if - - // Indicate that we are going to propagate check states up and down the hierarchy. - if FCheckPropagationCount = 0 then begin - // Do not enter tsCheckPropagation more than once - DoStateChange([tsCheckPropagation]); - BeginUpdate(); - end; - Inc(FCheckPropagationCount); - try - // Do actions which are associated with the given check state. - case CheckType of - // Check state change with additional consequences for check states of the children. - ctTriStateCheckBox: - begin - // Propagate state down to the children. - if toAutoTristateTracking in FOptions.AutoOptions then - case Value of - csUncheckedNormal: - if Node.ChildCount > 0 then - begin - Run := FirstChild; - CheckedCount := 0; - MixedCheckCount := 0; - UncheckedCount := 0; - while Assigned(Run) do - begin - if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then - begin - if not Self.GetCheckState(Run).IsDisabled() then - SetCheckState(Run, csUncheckedNormal); - // Check if the new child state was set successfully, otherwise we have to adjust the - // node's new check state accordingly. - case Self.GetCheckState(Run) of - csCheckedNormal, csCheckedDisabled: - Inc(CheckedCount); - csMixedNormal: - Inc(MixedCheckCount); - csUncheckedNormal, csUncheckedDisabled: - Inc(UncheckedCount); - end; - end; - Run := Run.NextSibling; - end; - - // If there is still a mixed state child node checkbox then this node must be mixed checked too. - if MixedCheckCount > 0 then - Value := csMixedNormal - else - // If nodes are normally checked child nodes then the unchecked count determines what - // to set for the node itself. - if CheckedCount > 0 then - if UncheckedCount > 0 then - Value := csMixedNormal - else - Value := csCheckedNormal; - end; - csCheckedNormal: - if Node.ChildCount > 0 then - begin - Run := FirstChild; - CheckedCount := 0; - MixedCheckCount := 0; - UncheckedCount := 0; - while Assigned(Run) do - begin - if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then - begin - if not Self.GetCheckState(Run).IsDisabled() then - SetCheckState(Run, csCheckedNormal); - // Check if the new child state was set successfully, otherwise we have to adjust the - // node's new check state accordingly. - case Self.GetCheckState(Run) of - csCheckedNormal: - Inc(CheckedCount); - csMixedNormal: - Inc(MixedCheckCount); - csUncheckedNormal: - Inc(UncheckedCount); - end; - end; - Run := Run.NextSibling; - end; - - // If there is still a mixed state child node checkbox then this node must be mixed checked too. - if MixedCheckCount > 0 then - Value := csMixedNormal - else - // If nodes are normally checked child nodes then the unchecked count determines what - // to set for the node itself. - if CheckedCount > 0 then - if UncheckedCount > 0 then - Value := csMixedNormal - else - Value := csCheckedNormal; - end; - end; - end; - // radio button check state change - ctRadioButton: - if Value = csCheckedNormal then - begin - Value := csCheckedNormal; - // Make sure only this node is checked. - Run := Parent.FirstChild; - while Assigned(Run) do - begin - if Run.CheckType = ctRadioButton then - Run.CheckState := csUncheckedNormal; - Run := Run.NextSibling; - end; - Invalidate; - end; - end; - - if Result then - CheckState := Value // Set new check state - else - CheckState := Self.GetCheckState(Node).GetUnpressed(); // Reset dynamic check state. - - // Propagate state up to the parent. - if not (vsInitialized in Parent.States) then - InitNode(Parent); - if (toAutoTristateTracking in FOptions.AutoOptions) and ([vsChecking, vsDisabled] * Parent.States = []) and - (CheckType in [ctCheckBox, ctTriStateCheckBox]) and (Parent <> FRoot) and - (Parent.CheckType = ctTriStateCheckBox) then - Result := CheckParentCheckState(Node, Value) - else - Result := True; - - InvalidateNode(Node); - finally - Dec(FCheckPropagationCount); // WL, 05.02.2004 - if FCheckPropagationCount = 0 then begin - // Allow state change event after all check operations finished - DoStateChange([], [tsCheckPropagation]); - EndUpdate(); - end; - end; - finally - Exclude(States, vsChecking); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CollectSelectedNodesLTR(MainColumn, NodeLeft, NodeRight: Integer; Alignment: TAlignment; - OldRect, NewRect: TRect): Boolean; - -// Helper routine used when a draw selection takes place. This version handles left-to-right directionality. -// In the process of adding or removing nodes the current selection is modified which requires to pack it after -// the function returns. Another side effect of this method is that a temporary list of nodes will be created -// (see also InternalCacheNode) which must be inserted into the current selection by the caller. - -var - Run, - NextNode: PVirtualNode; - TextRight, - TextLeft, - CurrentTop, - CurrentRight, - NextTop, - NextColumn, - NodeWidth, - Dummy: Integer; - MinY, MaxY: Integer; - LabelOffset: Integer; - IsInOldRect, - IsInNewRect: Boolean; - - // quick check variables for various parameters - DoSwitch, - AutoSpan: Boolean; - SimpleSelection: Boolean; - -begin - // A priori nothing changes. - Result := False; - - // Determine minimum and maximum vertical coordinates to limit iteration to. - MinY := Min(OldRect.Top, NewRect.Top); - MaxY := Max(OldRect.Bottom, NewRect.Bottom); - - // Initialize short hand variables to speed up tests below. - DoSwitch := ssCtrl in FDrawSelShiftState; - AutoSpan := FHeader.UseColumns and (toAutoSpanColumns in FOptions.AutoOptions); - SimpleSelection := toSimpleDrawSelection in FOptions.SelectionOptions; - // This is the node to start with. - Run := GetNodeAt(0, MinY, False, CurrentTop); - - if Assigned(Run) then - begin - LabelOffset := GetOffset(TVTElement.ofsLabel, Run); - - // ----- main loop - // Change selection depending on the node's rectangle being in the selection rectangle or not, but - // touch only those nodes which overlap either the old selection rectangle or the new one but not both. - repeat - // Collect offsets for check, normal and state images. - TextLeft := NodeLeft + LabelOffset; - NextTop := CurrentTop + Integer(NodeHeight[Run]); - - // Simple selection allows to draw the selection rectangle anywhere. No intersection with node captions is - // required. Only top and bottom bounds of the rectangle matter. - if SimpleSelection or (toFullRowSelect in FOptions.SelectionOptions) then - begin - IsInOldRect := (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom) and - ((FHeader.Columns.Count = 0) or (FHeader.Columns.TotalWidth > OldRect.Left)) and ((NodeLeft + LabelOffset) < OldRect.Right); - IsInNewRect := (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom) and - ((FHeader.Columns.Count = 0) or (FHeader.Columns.TotalWidth > NewRect.Left)) and ((NodeLeft + LabelOffset) < NewRect.Right); - end - else - begin - // The right column border might be extended if column spanning is enabled. - if AutoSpan then - begin - with FHeader.Columns do - begin - NextColumn := MainColumn; - repeat - Dummy := GetNextVisibleColumn(NextColumn); - if (Dummy = InvalidColumn) or not ColumnIsEmpty(Run, Dummy) or - (Items[Dummy].BidiMode <> bdLeftToRight) then - Break; - NextColumn := Dummy; - until False; - if NextColumn = MainColumn then - CurrentRight := NodeRight - else - GetColumnBounds(NextColumn, Dummy, CurrentRight); - end; - end - else - CurrentRight := NodeRight; - // Check if we need the node's width. This is the case when the node is not left aligned or the - // left border of the selection rectangle is to the right of the left node border. - if (TextLeft < OldRect.Left) or (TextLeft < NewRect.Left) or (Alignment <> taLeftJustify) then - begin - NodeWidth := DoGetNodeWidth(Run, MainColumn); - if NodeWidth >= (CurrentRight - TextLeft) then - TextRight := CurrentRight - else - case Alignment of - taLeftJustify: - TextRight := TextLeft + NodeWidth; - taCenter: - begin - TextLeft := (TextLeft + CurrentRight - NodeWidth) div 2; - TextRight := TextLeft + NodeWidth; - end; - else - // taRightJustify - TextRight := CurrentRight; - TextLeft := TextRight - NodeWidth; - end; - end - else - TextRight := CurrentRight; - - // Now determine whether we need to change the state. - IsInOldRect := (OldRect.Left <= TextRight) and (OldRect.Right >= TextLeft) and - (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom); - IsInNewRect := (NewRect.Left <= TextRight) and (NewRect.Right >= TextLeft) and - (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom); - end; - - if IsInOldRect xor IsInNewRect then - begin - Result := True; - if DoSwitch then - begin - if vsSelected in Run.States then - InternalRemoveFromSelection(Run) - else - InternalCacheNode(Run); - end - else - begin - if IsInNewRect then - InternalCacheNode(Run) - else - InternalRemoveFromSelection(Run); - end; - end; - CurrentTop := NextTop; - // Get next visible node and update left node position. - NextNode := GetNextVisibleNoInit(Run, True); - if NextNode = nil then - Break; - Inc(NodeLeft, CountLevelDifference(Run, NextNode) * Integer(FIndent)); - Run := NextNode; - until CurrentTop > MaxY; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CollectSelectedNodesRTL(MainColumn, NodeLeft, NodeRight: Integer; Alignment: TAlignment; - OldRect, NewRect: TRect): Boolean; - -// Helper routine used when a draw selection takes place. This version handles right-to-left directionality. -// See also comments in CollectSelectedNodesLTR. - -var - Run, - NextNode: PVirtualNode; - TextRight, - TextLeft, - CheckOffset, - CurrentTop, - CurrentLeft, - NextTop, - NextColumn, - NodeWidth, - Dummy: Integer; - MinY, MaxY: Integer; - IsInOldRect, - IsInNewRect: Boolean; - - // quick check variables for various parameters - WithCheck, - WithStateImages, - DoSwitch, - AutoSpan: Boolean; - SimpleSelection: Boolean; - -begin - // A priori nothing changes. - Result := False; - // Switch the alignment to the opposite value in RTL context. - ChangeBiDiModeAlignment(Alignment); - - // Determine minimum and maximum vertical coordinates to limit iteration to. - MinY := Min(OldRect.Top, NewRect.Top); - MaxY := Max(OldRect.Bottom, NewRect.Bottom); - - // Initialize short hand variables to speed up tests below. - DoSwitch := ssCtrl in FDrawSelShiftState; - WithCheck := (toCheckSupport in FOptions.MiscOptions) and Assigned(FCheckImages); - // Don't check the events here as descendant trees might have overriden the DoGetImageIndex method. - WithStateImages := Assigned(FStateImages) or Assigned(OnGetImageIndexEx); - if WithCheck then - CheckOffset := FCheckImages.Width + FImagesMargin - else - CheckOffset := 0; - AutoSpan := FHeader.UseColumns and (toAutoSpanColumns in FOptions.AutoOptions); - SimpleSelection := toSimpleDrawSelection in FOptions.SelectionOptions; - // This is the node to start with. - Run := GetNodeAt(0, MinY, False, CurrentTop); - - if Assigned(Run) then - begin - // The initial minimal left border is determined by the identation level of the node and is dynamically adjusted. - if toShowRoot in FOptions.PaintOptions then - Dec(NodeRight, Integer((GetNodeLevel(Run) + 1) * FIndent) + FMargin) - else - Dec(NodeRight, Integer(GetNodeLevel(Run) * FIndent) + FMargin); - - // ----- main loop - // Change selection depending on the node's rectangle being in the selection rectangle or not, but - // touch only those nodes which overlap either the old selection rectangle or the new one but not both. - repeat - // Collect offsets for check, normal and state images. - TextRight := NodeRight; - if WithCheck and (Run.CheckType <> ctNone) then - Dec(TextRight, CheckOffset); - Dec(TextRight, GetImageSize(Run, ikNormal, MainColumn).cx); - if WithStateImages then - Dec(TextRight, GetImageSize(Run, ikState, MainColumn).cx); - NextTop := CurrentTop + Integer(NodeHeight[Run]); - - // Simple selection allows to draw the selection rectangle anywhere. No intersection with node captions is - // required. Only top and bottom bounds of the rectangle matter. - if SimpleSelection then - begin - IsInOldRect := (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom); - IsInNewRect := (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom); - end - else - begin // The left column border might be extended if column spanning is enabled. - if AutoSpan then - begin - NextColumn := MainColumn; - repeat - Dummy := FHeader.Columns.GetPreviousVisibleColumn(NextColumn); - if (Dummy = InvalidColumn) or not ColumnIsEmpty(Run, Dummy) or - (FHeader.Columns[Dummy].BiDiMode = bdLeftToRight) then - Break; - NextColumn := Dummy; - until False; - if NextColumn = MainColumn then - CurrentLeft := NodeLeft - else - FHeader.Columns.GetColumnBounds(NextColumn, CurrentLeft, Dummy); - end - else - CurrentLeft := NodeLeft; - // Check if we need the node's width. This is the case when the node is not left aligned (in RTL context this // means actually right aligned) or the right border of the selection rectangle is to the left - // of the right node border. - if (TextRight > OldRect.Right) or (TextRight > NewRect.Right) or (Alignment <> taRightJustify) then - begin - NodeWidth := DoGetNodeWidth(Run, MainColumn); - if NodeWidth >= (TextRight - CurrentLeft) then - TextLeft := CurrentLeft - else - case Alignment of - taLeftJustify: - begin - TextLeft := CurrentLeft; - TextRight := TextLeft + NodeWidth; - end; - taCenter: - begin - TextLeft := (TextRight + CurrentLeft - NodeWidth) div 2; - TextRight := TextLeft + NodeWidth; - end; - else - // taRightJustify - TextLeft := TextRight - NodeWidth; - end; - end - else - TextLeft := CurrentLeft; - - // Now determine whether we need to change the state. - IsInOldRect := (OldRect.Right >= TextLeft) and (OldRect.Left <= TextRight) and - (NextTop > OldRect.Top) and (CurrentTop < OldRect.Bottom); - IsInNewRect := (NewRect.Right >= TextLeft) and (NewRect.Left <= TextRight) and - (NextTop > NewRect.Top) and (CurrentTop < NewRect.Bottom); - end; - - if IsInOldRect xor IsInNewRect then - begin - Result := True; - if DoSwitch then - begin - if vsSelected in Run.States then - InternalRemoveFromSelection(Run) - else - InternalCacheNode(Run); - end - else - begin - if IsInNewRect then - InternalCacheNode(Run) - else - InternalRemoveFromSelection(Run); - end; - end; - CurrentTop := NextTop; - // Get next visible node and update left node position. - NextNode := GetNextVisibleNoInit(Run, True); - if NextNode = nil then - Break; - Dec(NodeRight, CountLevelDifference(Run, NextNode) * Integer(FIndent)); - Run := NextNode; - until CurrentTop > MaxY; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ClearNodeBackground(const PaintInfo: TVTPaintInfo; UseBackground, Floating: Boolean; - R: TRect); - -// Erases a node's background depending on what the application decides to do. -// UseBackground determines whether or not to use the background picture, while Floating indicates -// that R is given in coordinates of the small node bitmap or the superordinated target bitmap used in PaintTree. - -var - BackColor: TColor; - EraseAction: TItemEraseAction; - Offset: TPoint; - -begin - BackColor := FColors.BackGroundColor; - with PaintInfo do - begin - EraseAction := eaDefault; - - if Floating then - begin - Offset := Point(-FEffectiveOffsetX, R.Top); - OffsetRect(R, 0, -Offset.Y); - end - else - Offset := Point(0, 0); - - DoBeforeItemErase(Canvas, Node, R, BackColor, EraseAction); - - with Canvas do - begin - case EraseAction of - eaNone: - ; - eaColor: - begin - // User has given a new background color. - Brush.Color := BackColor; - FillRect(R); - end; - else // eaDefault - if UseBackground then - begin - if toStaticBackground in TreeOptions.PaintOptions then - StaticBackground(FBackground, Canvas, Offset, R, FColors.BackGroundColor) - else - TileBackground(FBackground, Canvas, Offset, R, FColors.BackGroundColor); - end - else - begin - if (poDrawSelection in PaintOptions) and (toFullRowSelect in FOptions.SelectionOptions) and - (vsSelected in Node.States) and not (toUseBlendedSelection in FOptions.PaintOptions) and not - (tsUseExplorerTheme in FStates) then - begin - if toShowHorzGridLines in FOptions.PaintOptions then - begin - Brush.Color := BackColor; - FillRect(Rect(R.Left, R.Bottom - 1, R.Right, R.Bottom)); - Dec(R.Bottom); - end; - if Focused or (toPopupMode in FOptions.PaintOptions) then - begin - Brush.Color := FColors.FocusedSelectionColor; - Pen.Color := FColors.FocusedSelectionBorderColor; - end - else - begin - Brush.Color := FColors.UnfocusedSelectionColor; - Pen.Color := FColors.UnfocusedSelectionBorderColor; - end; - - with TWithSafeRect(R) do - RoundRect(Left, Top, Right, Bottom, FSelectionCurveRadius, FSelectionCurveRadius); - end - else - begin - Brush.Color := BackColor; - FillRect(R); - end; - end; - end; - DoAfterItemErase(Canvas, Node, R); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CompareNodePositions(Node1, Node2: PVirtualNode; ConsiderChildrenAbove: Boolean = False): Integer; - -// Tries hard and smart to quickly determine whether Node1's structural position is before Node2's position. -// If ConsiderChildrenAbove is True, the nodes will be compared with their visual order in mind. -// Returns 0 if Node1 = Node2, < 0 if Node1 is located before Node2 else > 0. - -var - Run1, - Run2: PVirtualNode; - Level1, - Level2: Cardinal; - -begin - Assert(Assigned(Node1) and Assigned(Node2), 'Nodes must never be nil.'); - - if Node1 = Node2 then - Result := 0 - else - begin - if HasAsParent(Node1, Node2) then - Result := IfThen(ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions), -1, 1) - else - if HasAsParent(Node2, Node1) then - Result := IfThen(ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions), 1, -1) - else - begin - // the given nodes are neither equal nor are they parents of each other, so go up to FRoot - // for each node and compare the child indices of the top level parents - // Note: neither Node1 nor Node2 can be FRoot at this point as this (a bit strange) circumstance would - // be caught by the previous code. - - // start lookup at the same level - Level1 := GetNodeLevel(Node1); - Level2 := GetNodeLevel(Node2); - Run1 := Node1; - while Level1 > Level2 do - begin - Run1 := Run1.Parent; - Dec(Level1); - end; - Run2 := Node2; - while Level2 > Level1 do - begin - Run2 := Run2.Parent; - Dec(Level2); - end; - - // now go up until we find a common parent node (loop will safely stop at FRoot if the nodes - // don't share a common parent) - while Run1.Parent <> Run2.Parent do - begin - Run1 := Run1.Parent; - Run2 := Run2.Parent; - end; - Result := Integer(Run1.Index) - Integer(Run2.Index); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DrawLineImage(const PaintInfo: TVTPaintInfo; X, Y, H, VAlign: Integer; Style: TVTLineType; - Reverse: Boolean); - -// Draws (depending on Style) one of the 5 line types of the tree. -// If Reverse is True then a right-to-left column is being drawn, hence horizontal lines must be mirrored. -// X and Y describe the left upper corner of the line image rectangle, while H denotes its height (and width). - -var - HalfWidth, - TargetX: Integer; - -begin - HalfWidth := (FIndent div 2); - if Reverse then - TargetX := 0 - else - TargetX := Integer(FIndent) + ScaledPixels(FImagesMargin); - - with PaintInfo.Canvas do - begin - case Style of - ltBottomRight: - begin - DrawDottedVLine(PaintInfo, Y + VAlign, Y + H, X + HalfWidth); - DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign); - end; - ltTopDown: - DrawDottedVLine(PaintInfo, Y, Y + H, X + HalfWidth); - ltTopDownRight: - begin - DrawDottedVLine(PaintInfo, Y, Y + H, X + HalfWidth); - DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign); - end; - ltRight: - DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign); - ltTopRight: - begin - DrawDottedVLine(PaintInfo, Y, Y + VAlign, X + HalfWidth); - DrawDottedHLine(PaintInfo, X + HalfWidth, X + TargetX, Y + VAlign); - end; - ltLeft: // left can also mean right for RTL context - if Reverse then - DrawDottedVLine(PaintInfo, Y, Y + H, X + Integer(FIndent)) - else - DrawDottedVLine(PaintInfo, Y, Y + H, X); - ltLeftBottom: - if Reverse then - begin - DrawDottedVLine(PaintInfo, Y, Y + H, X + Integer(FIndent)); - DrawDottedHLine(PaintInfo, X, X + Integer(FIndent), Y + H); - end - else - begin - DrawDottedVLine(PaintInfo, Y, Y + H, X); - DrawDottedHLine(PaintInfo, X, X + Integer(FIndent), Y + H); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.FindInPositionCache(Node: PVirtualNode; var CurrentPos: Cardinal): PVirtualNode; - -// Looks through the position cache and returns the node whose top position is the largest one which is smaller or equal -// to the position of the given node. - -var - L, H, I: Integer; - -begin - L := 0; - H := High(FPositionCache); - while L <= H do - begin - I := (L + H) shr 1; - if CompareNodePositions(FPositionCache[I].Node, Node) <= 0 then - L := I + 1 - else - H := I - 1; - end; - if L = 0 then // High(FPositionCache) = -1 - begin - Result := nil; - CurrentPos := 0; - end - else - begin - Result := FPositionCache[L - 1].Node; - CurrentPos := FPositionCache[L - 1].AbsoluteTop; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.FindInPositionCache(Position: Cardinal; var CurrentPos: Cardinal): PVirtualNode; - -// Looks through the position cache and returns the node whose top position is the largest one which is smaller or equal -// to the given vertical position. -// The returned node does not necessarily occupy the given position but is the nearest one to start -// iterating from to approach the real node for a given position. CurrentPos receives the actual position of the found -// node which is needed for further iteration. - -var - L, H, I: Integer; - -begin - L := 0; - H := High(FPositionCache); - while L <= H do - begin - I := (L + H) shr 1; - if FPositionCache[I].AbsoluteTop <= Position then - L := I + 1 - else - H := I - 1; - end; - if L = 0 then // High(FPositionCache) = -1 - begin - Result := nil; - CurrentPos := 0; - end - else - begin - Result := FPositionCache[L - 1].Node; - CurrentPos := FPositionCache[L - 1].AbsoluteTop; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.FixupTotalCount(Node: PVirtualNode); - -// Called after loading a subtree from stream. The child count in each node is already set but not -// their total count. - -var - Child: PVirtualNode; - -begin - // Initial total count is set to one on node creation. - Child := Node.FirstChild; - while Assigned(Child) do - begin - FixupTotalCount(Child); - Inc(Node.TotalCount, Child.TotalCount); - Child := Child.NextSibling; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.FixupTotalHeight(Node: PVirtualNode); - -// Called after loading a subtree from stream. The individual height of each node is set already, -// but their total height needs an adjustment depending on their visibility state. - -var - Child: PVirtualNode; - -begin - // Initial total height is set to the node height on load. - Child := Node.FirstChild; - - if vsExpanded in Node.States then - begin - while Assigned(Child) do - begin - FixupTotalHeight(Child); - if vsVisible in Child.States then - Inc(Node.TotalHeight, Child.TotalHeight); - Child := Child.NextSibling; - end; - end - else - begin - // The node is collapsed, so just update the total height of its child nodes. - while Assigned(Child) do - begin - FixupTotalHeight(Child); - Child := Child.NextSibling; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetBottomNode: PVirtualNode; - -begin - Result := GetNodeAt(0, ClientHeight - 1); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetCheckedCount: Integer; - -var - Node: PVirtualNode; - -begin - Result := 0; - Node := GetFirstChecked; - while Assigned(Node) do - begin - Inc(Result); - Node := GetNextChecked(Node); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetCheckState(Node: PVirtualNode): TCheckState; - -begin - if Assigned(FOnBeforeGetCheckState) then - FOnBeforeGetCheckState(Self, Node); - - Result := Node.CheckState; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetCheckType(Node: PVirtualNode): TCheckType; - -begin - Result := Node.CheckType; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetChildCount(Node: PVirtualNode): Cardinal; -begin - if (Node = nil) or (Node = FRoot) then - Exit(FRoot.ChildCount); - if not GetChildrenInitialized(Node) then - InitChildren(Node); - Exit(Node.ChildCount); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetChildrenInitialized(Node: PVirtualNode): Boolean; - -begin - Result := not (vsHasChildren in Node.States) or (Node.ChildCount > 0); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetCutCopyCount: Integer; - -var - Node: PVirtualNode; - -begin - Result := 0; - Node := GetFirstCutCopy; - while Assigned(Node) do - begin - Inc(Result); - Node := GetNextCutCopy(Node); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetDisabled(Node: PVirtualNode): Boolean; - -begin - Result := Assigned(Node) and (vsDisabled in Node.States); -end; - -//---------------------------------------------------------------------------------------------------------------------- -// whether the sync of checkbox with selection is allowed for this node -function TBaseVirtualTree.GetSyncCheckstateWithSelection(Node: PVirtualNode): Boolean; - -begin - Result := (toSyncCheckboxesWithSelection in FOptions.SelectionOptions) - and (toCheckSupport in FOptions.MiscOptions) - and Assigned(FCheckImages) - and (Node.CheckType = ctCheckBox); ; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetDragManager: IVTDragManager; - -// Returns the internal drag manager interface. If this does not yet exist then it is created here. - -begin - if FDragManager = nil then - begin - FDragManager := DoCreateDragManager; - if FDragManager = nil then - FDragManager := TVTDragManager.Create(Self); - end; - - Result := FDragManager; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetExpanded(Node: PVirtualNode): Boolean; - -begin - if Assigned(Node) then - Result := vsExpanded in Node.States - else - Result := False; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFiltered(Node: PVirtualNode): Boolean; - -begin - Result := vsFiltered in Node.States; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFullyVisible(Node: PVirtualNode): Boolean; - -// Determines whether the given node has the visibility flag set as well as all its parents are expanded. - -begin - Assert(Assigned(Node), 'Invalid parameter.'); - Result := vsVisible in Node.States; - if Result and (Node <> FRoot) then - Result := VisiblePath[Node]; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetHasChildren(Node: PVirtualNode): Boolean; - -begin - if Assigned(Node) then - Result := vsHasChildren in Node.States - else - Result := vsHasChildren in FRoot.States; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetMultiline(Node: PVirtualNode): Boolean; - -begin - Result := Assigned(Node) and (Node <> FRoot) and (vsMultiline in Node.States); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNodeHeight(Node: PVirtualNode): Cardinal; - -begin - if Assigned(Node) and (Node <> FRoot) then - begin - if (toVariableNodeHeight in FOptions.MiscOptions) and not (vsDeleting in Node.States) then - begin - if not (vsInitialized in Node.States) then - InitNode(Node); - - // Ensure the node's height is determined. - MeasureItemHeight(Self.Canvas, Node); - end; - Result := Node.NodeHeight; - end - else - Result := 0; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNodeParent(Node: PVirtualNode): PVirtualNode; - -begin - if Assigned(Node) and (Node.Parent <> FRoot) then - Result := Node.Parent - else - Result := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetOffset(pElement: TVTElement; pNode: PVirtualNode): integer; -// Calculates the offset of the given element -var - lOffsets: TVTOffsets; -begin - GetOffsets(pNode, lOffsets, pElement); - Exit(lOffsets[pElement]); -end; - -procedure TBaseVirtualTree.GetOffsets(pNode: PVirtualNode; out pOffsets: TVTOffsets; pElement: TVTElement = TVTElement.ofsEndOfClientArea; pColumn: Integer = NoColumn); -// Calculates the offset up to the given element and supplies them in an array. -var - lNodeLevel: Integer; -begin - // If no specific column was given, assume the main column - if pColumn = -1 then - pColumn := Header.MainColumn; - - // Left Margin - pOffsets[TVTElement.ofsMargin] := FMargin; - if pElement = ofsMargin then - exit; - pOffsets[TVTElement.ofsCheckBox] := FMargin + fImagesMargin; - if (pColumn = Header.MainColumn) then - begin - if not (toFixedIndent in TreeOptions.PaintOptions) then begin - // plus Indent - lNodeLevel := GetNodeLevel(pNode); - if toShowRoot in FOptions.PaintOptions then - Inc(lNodeLevel); - end - else - lNodeLevel := 1; - Inc(pOffsets[TVTElement.ofsCheckBox], lNodeLevel * Integer(FIndent)); - // toggle buttons - pOffsets[TVTElement.ofsToggleButton] := pOffsets[TVTElement.ofsCheckBox] - fImagesMargin - ((Integer(FIndent) - FPlusBM.Width) div 2) + 1 - FPlusBM.Width; //Compare PaintTree() relative line 107 - end;//if MainColumn - - // The area in which the toggle buttons are painted must have exactly the size of one indent level - if pElement <= TVTElement.ofsCheckBox then - exit; - - // right of checkbox, left of state image - if (toCheckSupport in FOptions.MiscOptions) and Assigned(FCheckImages) and (pNode.CheckType <> ctNone) and (pColumn = Header.MainColumn) then - pOffsets[TVTElement.ofsStateImage] := pOffsets[TVTElement.ofsCheckBox] + FCheckImages.Width + fImagesMargin - else - pOffsets[TVTElement.ofsStateImage] := pOffsets[TVTElement.ofsCheckBox]; - if pElement = TVTElement.ofsStateImage then - exit; - // right of left image, left of normal image - pOffsets[TVTElement.ofsImage] := pOffsets[TVTElement.ofsStateImage] + GetImageSize(pNode, TVTImageKind.ikState, pColumn).cx; - if pElement = TVTElement.ofsImage then - exit; - // label - pOffsets[TVTElement.ofsLabel] := pOffsets[TVTElement.ofsImage] + GetImageSize(pNode, TVTImageKind.ikNormal, pColumn).cx; - pOffsets[TVTElement.ofsText] := pOffsets[TVTElement.ofsLabel] + FTextMargin; - Dec(pOffsets[TVTElement.ofsText]); //TODO: This should no longer be necessary once issue #369 is resolved. - if pElement <= TVTElement.ofsText then - exit; - - // End of text - pOffsets[TVTElement.ofsRightOfText] := pOffsets[TVTElement.ofsText] + DoGetNodeWidth(pNode, pColumn) + DoGetNodeExtraWidth(pNode, pColumn); - - // end of client area - pOffsets[TVTElement.ofsEndOfClientArea] := Max(FRangeX, ClientWidth) - FTextMargin; -end; - -function TBaseVirtualTree.GetOffsetXY: TPoint; - -begin - Result := Point(FOffsetX, FOffsetY); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetRangeX: Cardinal; -begin - Result := Max(0, FRangeX); -end; - -function TBaseVirtualTree.GetRootNodeCount: Cardinal; - -begin - Result := FRoot.ChildCount; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetSelected(Node: PVirtualNode): Boolean; - -begin - Result := Assigned(Node) and (vsSelected in Node.States); -end; - -function TBaseVirtualTree.GetSelectedData: TArray; -var - lItem: PVirtualNode; - i: Integer; -begin - SetLEngth(Result, Self.SelectedCount); - i := 0; - lItem := Self.GetFirstSelected; - while Assigned(lItem) do - begin - Result[i] := Self.GetNodeData(lItem); - lItem := Self.GetNextSelected(lItem); - Inc(i); - end; - SetLength(Result, i); // See issue #927, SelectedCount may not yet be updated. -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetTopNode: PVirtualNode; - -var - Dummy: Integer; - -begin - Result := GetNodeAt(0, 0, True, Dummy); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetTotalCount(): Cardinal; - -begin - Assert(GetCurrentThreadId = MainThreadId, 'UI controls may only be used in UI thread.'); // FUpdateCount is not thread-safe! So do not write it in non-UI threads. - Inc(FUpdateCount); - try - ValidateNode(FRoot, True); - finally - Dec(FUpdateCount); - end; - // The root node itself doesn't count as node. - Result := FRoot.TotalCount - 1; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetVclStyleEnabled: Boolean; -begin - Exit(FVclStyleEnabled); -end; - -function TBaseVirtualTree.GetVerticalAlignment(Node: PVirtualNode): Byte; - -begin - Result := Node.Align; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetVisible(Node: PVirtualNode): Boolean; - -// Determines if the given node is marked as being visible. - -begin - if Node = nil then - Node := FRoot; - - if not (vsInitialized in Node.States) then - InitNode(Node); - - Result := vsVisible in Node.States; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetVisiblePath(Node: PVirtualNode): Boolean; - -// Determines if all parents of the given node are expanded and have the visibility flag set. - -begin - Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameters.'); - - // FRoot is always expanded - repeat - Node := Node.Parent; - until (Node = FRoot) or not (vsExpanded in Node.States) or not (vsVisible in Node.States); - - Result := Node = FRoot; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.HandleClickSelection(LastFocused, NewNode: PVirtualNode; Shift: TShiftState; - DragPending: Boolean); - -// Handles multi-selection with mouse click. - -begin - // Ctrl key down - if ssCtrl in Shift then - begin - if ssShift in Shift then - begin - SelectNodes(FRangeAnchor, NewNode, True); - end - else - begin - if not (toSiblingSelectConstraint in FOptions.SelectionOptions) then - FRangeAnchor := NewNode; - // Delay selection change if a drag operation is pending. - // Otherwise switch selection state here. - if DragPending then - DoStateChange([tsToggleFocusedSelection]) - else - if vsSelected in NewNode.States then - RemoveFromSelection(NewNode) - else - AddToSelection(NewNode, True); - end; - end - else - // Shift key down - if ssShift in Shift then - begin - if FRangeAnchor = nil then - FRangeAnchor := FRoot.FirstChild; - - // select node range - if Assigned(FRangeAnchor) then - begin - SelectNodes(FRangeAnchor, NewNode, False); - Invalidate; - end; - end - else - begin - // any other case - if not (vsSelected in NewNode.States) then - AddToSelection(NewNode, True); - // assign new reference item - FRangeAnchor := NewNode; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.HandleDrawSelection(X, Y: Integer): Boolean; - -// Handles multi-selection with a focus rectangle. -// Result is True if something changed in selection. - -var - OldRect, - NewRect: TRect; - MainColumn: TColumnIndex; - MaxValue: Integer; - - // limits of a node and its text - NodeLeft, - NodeRight: Integer; - - // alignment and directionality - CurrentBidiMode: TBidiMode; - CurrentAlignment: TAlignment; - -begin - Result := False; - - // Selection changes are only done if the user drew a selection rectangle large - // enough to exceed the threshold. - if (FRoot.TotalCount > 1) and (tsDrawSelecting in FStates) then - begin - // Effective handling of node selection is done by using two rectangles stored in FSelectRec. - OldRect := OrderRect(FLastSelRect); - NewRect := OrderRect(FNewSelRect); - ClearTempCache; - - MainColumn := FHeader.MainColumn; - - // Alignment and bidi mode determine where the node text is located within a node. - if MainColumn <= NoColumn then - begin - CurrentBidiMode := BidiMode; - CurrentAlignment := Alignment; - end - else - begin - CurrentBidiMode := FHeader.Columns[MainColumn].BidiMode; - CurrentAlignment := FHeader.Columns[MainColumn].Alignment; - end; - - // Determine initial left border of first node (take column reordering into account). - if FHeader.UseColumns then - begin - // The mouse coordinates don't include any horizontal scrolling hence take this also - // out from the returned column position. - NodeLeft := FHeader.Columns[MainColumn].Left + FEffectiveOffsetX; - NodeRight := NodeLeft + FHeader.Columns[MainColumn].Width; - end - else - begin - NodeLeft := 0 + FEffectiveOffsetX; - NodeRight := NodeLeft + ClientWidth; - end; - if CurrentBidiMode = bdLeftToRight then - Result := CollectSelectedNodesLTR(MainColumn, NodeLeft, NodeRight, CurrentAlignment, OldRect, NewRect) - else - Result := CollectSelectedNodesRTL(MainColumn, NodeLeft, NodeRight, CurrentAlignment, OldRect, NewRect); - end; - - if Result then - begin - // Do some housekeeping if there was a change. - MaxValue := PackArray(FSelection, FSelectionCount); - if MaxValue > -1 then - begin - FSelectionCount := MaxValue; - SetLength(FSelection, FSelectionCount); - end; - if FTempNodeCount > 0 then - begin - if tsClearOnNewSelection in fStates then - begin - DoStateChange([], [tsClearOnNewSelection]); - ClearSelection(False); - end; - - AddToSelection(FTempNodeCache, FTempNodeCount); - ClearTempCache; - end; - - Change(nil); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.HasVisibleNextSibling(Node: PVirtualNode): Boolean; - -// Helper method to determine if the given node has a visible next sibling. This is needed to -// draw correct tree lines. - -begin - // Check if there is a sibling at all. - Result := Assigned(Node.NextSibling); - - if Result then - begin - repeat - Node := Node.NextSibling; - Result := IsEffectivelyVisible[Node]; - until Result or (Node.NextSibling = nil); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.HasVisiblePreviousSibling(Node: PVirtualNode): Boolean; - -// Helper method to determine if the given node has a visible previous sibling. This is needed to -// draw correct tree lines. - -begin - // Check if there is a sibling at all. - Result := Assigned(Node.PrevSibling); - - if Result then - begin - repeat - Node := Node.PrevSibling; - Result := IsEffectivelyVisible[Node]; - until Result or (Node.PrevSibling = nil); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ImageListChange(Sender: TObject); - -begin - if not (csDestroying in ComponentState) then - Invalidate; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InitializeFirstColumnValues(var PaintInfo: TVTPaintInfo); - -// Determines initial index, position and cell size of the first visible column. - -begin - PaintInfo.Column := FHeader.Columns.GetFirstVisibleColumn; - with FHeader.Columns, PaintInfo do - begin - if Column > NoColumn then - begin - CellRect.Right := CellRect.Left + Items[Column].Width; - Position := Items[Column].Position; - end - else - Position := 0; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InitRecursive(Node: PVirtualNode; Levels: Cardinal = MaxInt; pVisibleOnly: Boolean = True); - -// Initializes a node and optionally its children up to a certain level. -// The sepcified number of levels are latrive to the givne Node. - -var - Run: PVirtualNode; -begin - if not Assigned(Node) then - Node := FRoot; - - if (Node <> FRoot) and not (vsInitialized in Node.States) then - InitNode(Node); - if (Levels = 0) or (pVisibleOnly and not (vsExpanded in Node.States)) then - exit; - Run := Node.FirstChild; - - while Assigned(Run) do - begin - InitRecursive(Run, Levels - 1, pVisibleOnly); - Run := Run.NextSibling; - end; -end; - -procedure TBaseVirtualTree.InitRootNode(OldSize: Cardinal = 0); - -// Reinitializes the root node. - -var - NewSize: Cardinal; - -begin - NewSize := TreeNodeSize + FTotalInternalDataSize; - if FRoot = nil then - FRoot := AllocMem(NewSize) - else - begin - ReallocMem(FRoot, NewSize); - ZeroMemory(PByte(FRoot) + OldSize, NewSize - OldSize); - end; - - with FRoot^ do - begin - // Indication that this node is the root node. - PrevSibling := FRoot; - NextSibling := FRoot; - Parent := Pointer(Self); - States := [vsInitialized, vsExpanded, vsHasChildren, vsVisible]; - TotalHeight := FDefaultNodeHeight; - TotalCount := 1; - NodeHeight := FDefaultNodeHeight; - Align := 50; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InterruptValidation(pWaitForValidationTermination: Boolean = True); - -var - WasValidating: Boolean; -begin - DoStateChange([tsStopValidation], [tsUseCache]); - - // Check the worker thread existance. It might already be gone (usually on destruction of the last tree). - WasValidating := (tsValidating in FStates); - TWorkerThread.RemoveTree(Self, pWaitForValidationTermination); - if WasValidating then - InvalidateCache(); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.IsFirstVisibleChild(Parent, Node: PVirtualNode): Boolean; - -// Helper method to check if Node is the same as the first visible child of Parent. - -var - Run: PVirtualNode; - -begin - // Find first visible child. - Run := Parent.FirstChild; - while Assigned(Run) and not IsEffectivelyVisible[Run] do - Run := Run.NextSibling; - - Result := Assigned(Run) and (Run = Node); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.IsLastVisibleChild(Parent, Node: PVirtualNode): Boolean; - -// Helper method to check if Node is the same as the last visible child of Parent. - -var - Run: PVirtualNode; - -begin - // Find last visible child. - Run := Parent.LastChild; - while Assigned(Run) and not IsEffectivelyVisible[Run] do - Run := Run.PrevSibling; - - Result := Assigned(Run) and (Run = Node); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.MakeNewNode: PVirtualNode; - -var - Size: Cardinal; - -begin - Size := TreeNodeSize; - if not (csDesigning in ComponentState) then - begin // Make sure FNodeDataSize is valid. - if FNodeDataSize <= 0 then - ValidateNodeDataSize(FNodeDataSize); - - // Take record alignment into account. - Inc(Size, FNodeDataSize); - end//not csDesigning - else - Inc(Size, SizeOf(Pointer)); // Fixes #702 - - - Result := AllocMem(Size + FTotalInternalDataSize); - - // Fill in some default values. - with Result^ do - begin - TotalCount := 1; - TotalHeight := FDefaultNodeHeight; - NodeHeight := FDefaultNodeHeight; - States := [vsVisible]; - Align := 50; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.PackArray({*}const TheArray: TNodeArray; Count: Integer): Integer; assembler; -// *This is an optimization to get as near as possible with the PUREPASCAL code without the -// compiler generating a _DynArrayAddRef call. We still modify the array's content via pointers. - -// Removes all entries from the selection array which are no longer in use. The selection array must be sorted for this -// algo to work. Values which must be removed are marked with bit 0 (LSB) set. This little trick works because memory -// is always allocated DWORD aligned. Since the selection array must be sorted while determining the entries to be -// removed it is much more efficient to increment the entry in question instead of setting it to nil (which would break -// the ordered appearance of the list). -// -// On enter EAX contains self reference, EDX the address to TheArray and ECX Count -// The returned value is the number of remaining entries in the array, so the caller can reallocate (shorten) -// the selection array if needed or -1 if nothing needs to be changed. - -{$ifdef CPUX64} -var - Source, Dest: ^PVirtualNode; - ConstOne: NativeInt; -begin - Source := Pointer(TheArray); - ConstOne := 1; - Result := 0; - // Do the fastest scan possible to find the first entry - while (Count <> 0) and {not Odd(NativeInt(Source^))} (NativeInt(Source^) and ConstOne = 0) do - begin - Inc(Result); - Inc(Source); - Dec(Count); - end; - - if Count <> 0 then - begin - Dest := Source; - repeat - // Skip odd entries - if {not Odd(NativeInt(Source^))} NativeInt(Source^) and ConstOne = 0 then - begin - Dest^ := Source^; - Inc(Result); - Inc(Dest); - end; - Inc(Source); // Point to the next entry - Dec(Count); - until Count = 0; - end; -end; -{$else} -asm - PUSH EBX - PUSH EDI - PUSH ESI - MOV ESI, EDX - MOV EDX, -1 - JCXZ @@Finish // Empty list? - INC EDX // init remaining entries counter - MOV EDI, ESI // source and destination point to the list memory - MOV EBX, 1 // use a register instead of immediate operant to check against -@@PreScan: - TEST [ESI], EBX // do the fastest scan possible to find the first entry - // which must be removed - JNZ @@DoMainLoop - INC EDX - ADD ESI, 4 - DEC ECX - JNZ @@PreScan - JMP @@Finish - -@@DoMainLoop: - MOV EDI, ESI -@@MainLoop: - TEST [ESI], EBX // odd entry? - JNE @@Skip // yes, so skip this one - MOVSD // else move the entry to new location - INC EDX // count the moved entries - DEC ECX - JNZ @@MainLoop // do it until all entries are processed - JMP @@Finish - -@@Skip: - ADD ESI, 4 // point to the next entry - DEC ECX - JNZ @@MainLoop // do it until all entries are processed -@@Finish: - MOV EAX, EDX // prepare return value - POP ESI - POP EDI - POP EBX -end; -{$endif CPUX64} - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.PrepareBitmaps(NeedButtons, NeedLines: Boolean); - -// initializes the contents of the internal bitmaps - -const - LineBitsDotted: array [0..8] of Word = ($55, $AA, $55, $AA, $55, $AA, $55, $AA, $55); - LineBitsSolid: array [0..7] of Word = (0, 0, 0, 0, 0, 0, 0, 0); - -var - PatternBitmap: HBITMAP; - Bits: Pointer; - Size: TSize; - Theme: HTHEME; - R: TRect; - - //--------------- local function -------------------------------------------- - - procedure FillBitmap (ABitmap: TBitmap); - begin - with ABitmap, Canvas do - begin - SetSize(Size.cx, Size.cy); - - if IsWinVistaOrAbove and (tsUseThemes in FStates) and (toUseExplorerTheme in FOptions.PaintOptions) or VclStyleEnabled then - begin - if (FHeader.MainColumn > NoColumn) then - Brush.Color := FHeader.Columns[FHeader.MainColumn].GetEffectiveColor - else - Brush.Color := FColors.BackGroundColor; - end - else - Brush.Color := clFuchsia; - - Transparent := True; - TransparentColor := Brush.Color; - - FillRect(Rect(0, 0, Width, Height)); - end; - end; - - //--------------- end local function ---------------------------------------- - -const - cMinExpandoHeight = 11; // pixels @100% -begin - if VclStyleEnabled and (seClient in StyleElements) then - begin - if NeedButtons then begin - if StyleServices.GetElementSize(FPlusBM.Canvas.Handle, StyleServices.GetElementDetails(tcbCategoryGlyphClosed), TElementSize.esActual, Size) then - begin - Size.cx := Max(Size.cx, cMinExpandoHeight); // Use min size of 11, see issue #1035 / RSP-33715 - Size.cx := ScaledPixels(Size.cx) // I would have expected that the returned value is dpi-sclaed, but this is not the case in RAD Studio 10.4.1. See issue #984 - end - else - Size.cx := ScaledPixels(cMinExpandoHeight); - Size.cy := Size.cx; - FillBitmap(FPlusBM); - FillBitmap(FHotPlusBM); - FillBitmap(FSelectedHotPlusBM); - FillBitmap(FMinusBM); - FillBitmap(FHotMinusBM); - FillBitmap(FSelectedHotMinusBM); - R := Rect(0,0,Size. cx,Size.cy); - // tcbCategoryGlyphClosed, tcbCategoryGlyphOpened from CategoryButtons - StyleServices.DrawElement(FPlusBM.Canvas.Handle, StyleServices.GetElementDetails(tcbCategoryGlyphClosed), R {$IF CompilerVersion >= 34}, nil, FCurrentPPI{$IFEND}); - StyleServices.DrawElement(FMinusBM.Canvas.Handle, StyleServices.GetElementDetails(tcbCategoryGlyphOpened), R {$IF CompilerVersion >= 34}, nil, FCurrentPPI{$IFEND}); - FHotMinusBM.Canvas.Draw(0, 0, FMinusBM); - FSelectedHotMinusBM.Canvas.Draw(0, 0, FMinusBM); - FHotPlusBM.Canvas.Draw(0, 0, FPlusBM); - FSelectedHotPlusBM.Canvas.Draw(0, 0, FPlusBM); - if Assigned(FOnPrepareButtonImages) then - FOnPrepareButtonImages(Self, FPlusBM, FHotPlusBM, FSelectedHotPlusBM, FMinusBM, FHotMinusBM, FSelectedHotMinusBM, size); - end;//if NeedButtons - end// if VclStyleEnabled - else - begin // No stlye - Size.cx := ScaledPixels(9); - Size.cy := ScaledPixels(9); - if tsUseThemes in FStates then - begin - R := Rect(0, 0, 100, 100); - {$if CompilerVersion >= 33} - if TOSVersion.Check(10) and (TOSVersion.Build >= 15063) then - Theme := OpenThemeDataForDPI(Handle, 'TREEVIEW', Self.FCurrentPPI) - else - Theme := OpenThemeData(Handle, 'TREEVIEW'); - {$else} - Theme := OpenThemeData(Handle, 'TREEVIEW'); - {$ifend} - GetThemePartSize(Theme, FPlusBM.Canvas.Handle, TVP_GLYPH, GLPS_OPENED, @R, TS_TRUE, Size); - end - else - Theme := 0; - - if NeedButtons then - begin - //VCL Themes do not really have ability to provide tree plus/minus images when not using the - //windows theme. The bitmap style designer doesn't have any elements for for them, and you - //cannot name any elements you add, which makes it useless. - //To mitigate this, Hook up the OnPrepareButtonImages and draw them yourself. - if Assigned(FOnPrepareButtonImages) then - begin - FillBitmap(FPlusBM); - FillBitmap(FHotPlusBM); - FillBitmap(FSelectedHotPlusBM); - FillBitmap(FMinusBM); - FillBitmap(FHotMinusBM); - FillBitmap(FSelectedHotMinusBM); - FOnPrepareButtonImages(Self, FPlusBM, FHotPlusBM, FSelectedHotPlusBM, FMinusBM, FHotMinusBM, FSelectedHotMinusBM, size); - end - else - begin - with FMinusBM, Canvas do - begin - // box is always of odd size - FillBitmap(FMinusBM); - FillBitmap(FHotMinusBM); - FillBitmap(FSelectedHotMinusBM); - // Weil die selbstgezeichneten Bitmaps sehen im Vcl Style scheiße aus - // Because the self-drawn bitmaps view Vcl Style shit - if Theme = 0 then - begin - if not(tsUseExplorerTheme in FStates) then - begin - if FButtonStyle = bsTriangle then - begin - Brush.Color := clBlack; - Pen.Color := clBlack; - Polygon([Point(0, 2), Point(8, 2), Point(4, 6)]); - end - else - begin - // Button style is rectangular. Now ButtonFillMode determines how to fill the interior. - if FButtonFillMode in [fmTreeColor, fmWindowColor, fmTransparent] then - begin - case FButtonFillMode of - fmTreeColor: - Brush.Color := FColors.BackGroundColor; - fmWindowColor: - Brush.Color := clWindow; - end; - Pen.Color := FColors.TreeLineColor; - Rectangle(0, 0, Width, Height); - Pen.Color := FColors.NodeFontColor; - MoveTo(2, Width div 2); - LineTo(Width - 2, Width div 2); - end - else - FMinusBM.Handle := LoadBitmap(HInstance, 'VT_XPBUTTONMINUS'); - end; - FHotMinusBM.Canvas.Draw(0, 0, FMinusBM); - FSelectedHotMinusBM.Canvas.Draw(0, 0, FMinusBM); - end; - end; - end; - with FPlusBM, Canvas do - begin - FillBitmap(FPlusBM); - FillBitmap(FHotPlusBM); - FillBitmap(FSelectedHotPlusBM); - if Theme = 0 then - begin - if not(tsUseExplorerTheme in FStates) then - begin - if FButtonStyle = bsTriangle then - begin - Brush.Color := clBlack; - Pen.Color := clBlack; - Polygon([Point(2, 0), Point(6, 4), Point(2, 8)]); - end - else - begin - // Button style is rectangular. Now ButtonFillMode determines how to fill the interior. - if FButtonFillMode in [fmTreeColor, fmWindowColor, fmTransparent] then - begin - case FButtonFillMode of - fmTreeColor: - Brush.Color := FColors.BackGroundColor; - fmWindowColor: - Brush.Color := clWindow; - end; - Pen.Color := FColors.TreeLineColor; - Rectangle(0, 0, Width, Height); - Pen.Color := FColors.NodeFontColor; - MoveTo(2, Width div 2); - LineTo(Width - 2, Width div 2); - MoveTo(Width div 2, 2); - LineTo(Width div 2, Width - 2); - end - else - FPlusBM.Handle := LoadBitmap(HInstance, 'VT_XPBUTTONPLUS'); - end; - FHotPlusBM.Canvas.Draw(0, 0, FPlusBM); - FSelectedHotPlusBM.Canvas.Draw(0, 0, FPlusBM); - end; - end; - end; - - - // Overwrite glyph images if theme is active. - if (tsUseThemes in FStates) and (Theme <> 0) then - begin - R := Rect(0, 0, Size.cx, Size.cy); - DrawThemeBackground(Theme, FPlusBM.Canvas.Handle, TVP_GLYPH, GLPS_CLOSED, R, nil); - DrawThemeBackground(Theme, FMinusBM.Canvas.Handle, TVP_GLYPH, GLPS_OPENED, R, nil); - if tsUseExplorerTheme in FStates then - begin - DrawThemeBackground(Theme, FHotPlusBM.Canvas.Handle, TVP_HOTGLYPH, GLPS_CLOSED, R, nil); - DrawThemeBackground(Theme, FSelectedHotPlusBM.Canvas.Handle, TVP_HOTGLYPH, GLPS_CLOSED, R, nil); - DrawThemeBackground(Theme, FHotMinusBM.Canvas.Handle, TVP_HOTGLYPH, GLPS_OPENED, R, nil); - DrawThemeBackground(Theme, FSelectedHotMinusBM.Canvas.Handle, TVP_HOTGLYPH, GLPS_OPENED, R, nil); - end - else - begin - FHotPlusBM.Canvas.Draw(0, 0, FPlusBM); - FSelectedHotPlusBM.Canvas.Draw(0, 0, FPlusBM); - FHotMinusBM.Canvas.Draw(0, 0, FMinusBM); - FSelectedHotMinusBM.Canvas.Draw(0, 0, FMinusBM); - end; - end; - end; - if tsUseThemes in FStates then - CloseThemeData(Theme); - end;// if NeedButtons - end;// else - - if NeedLines then - begin - if FDottedBrush <> 0 then - DeleteObject(FDottedBrush); - case FLineStyle of - lsDotted: - Bits := @LineBitsDotted; - lsSolid: - Bits := @LineBitsSolid; - else // lsCustomStyle - Bits := @LineBitsDotted; - DoGetLineStyle(Bits); - end; - PatternBitmap := CreateBitmap(8, 8, 1, 1, Bits); - FDottedBrush := CreatePatternBrush(PatternBitmap); - DeleteObject(PatternBitmap); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetAlignment(const Value: TAlignment); - -begin - if FAlignment <> Value then - begin - FAlignment := Value; - if not (csLoading in ComponentState) then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetAnimationDuration(const Value: Cardinal); - -begin - FAnimationDuration := Value; - if FAnimationDuration = 0 then - FOptions.AnimationOptions := FOptions.AnimationOptions - [toAnimatedToggle] - else - FOptions.AnimationOptions := FOptions.AnimationOptions + [toAnimatedToggle] -end; - -//---------------------------------------------------------------------------------------------------------------------- -{ New, Support for transparent background: - * Image types: BMP, PNG, GIF, ICO, EMF, TIFF and WMF are automatically identified to support transparent background - * Also detects certain third party image classes registered for PNG, GIF and other image types so that the - transparency related code is used for them. See the code below. - * If some other third party image class is registered that is not detected, - set the flag BackgroundTransparentExternalType explicitly in order to properly do - transparent painting. -} -procedure TBaseVirtualTree.SetBackground(const Value: TPicture); - -begin - FBackground.Assign(Value); - Invalidate; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetBackGroundImageTransparent(const Value: Boolean); - -begin - if Value <> FBackGroundImageTransparent then - begin - FBackGroundImageTransparent := Value; - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetBackgroundOffset(const Index, Value: Integer); - -begin - case Index of - 0: - if FBackgroundOffsetX <> Value then - begin - FBackgroundOffsetX := Value; - Invalidate; - end; - 1: - if FBackgroundOffsetY <> Value then - begin - FBackgroundOffsetY := Value; - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetBorderStyle(Value: TBorderStyle); - -begin - if FBorderStyle <> Value then - begin - FBorderStyle := Value; - RecreateWnd; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetBottomNode(Node: PVirtualNode); - -var - Run: PVirtualNode; - R: TRect; - -begin - if Assigned(Node) then - begin - // make sure all parents of the node are expanded - Run := Node.Parent; - while Run <> FRoot do - begin - if not (vsExpanded in Run.States) then - ToggleNode(Run); - Run := Run.Parent; - end; - R := GetDisplayRect(Node, FHeader.MainColumn, True); - DoSetOffsetXY(Point(FOffsetX, FOffsetY + ClientHeight - R.Top - Integer(NodeHeight[Node])), - [suoRepaintScrollBars, suoUpdateNCArea]); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetBottomSpace(const Value: Cardinal); - -begin - if FBottomSpace <> Value then - begin - FBottomSpace := Value; - UpdateVerticalScrollBar(True); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetButtonFillMode(const Value: TVTButtonFillMode); - -begin - if FButtonFillMode <> Value then - begin - FButtonFillMode := Value; - if not (csLoading in ComponentState) then - begin - PrepareBitmaps(True, False); - if HandleAllocated then - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetButtonStyle(const Value: TVTButtonStyle); - -begin - if FButtonStyle <> Value then - begin - FButtonStyle := Value; - if not (csLoading in ComponentState) then - begin - PrepareBitmaps(True, False); - if HandleAllocated then - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetCheckState(Node: PVirtualNode; Value: TCheckState); - -begin - if (Node.CheckState <> Value) and DoChecking(Node, Value) then - DoCheckClick(Node, Value); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetCheckStateForAll(aCheckState: TCheckState; pSelectedOnly: Boolean; pExcludeDisabled: Boolean = True); - -// Changes the check state for all or for all seledcted nodes. -// aCheckState: The new check state. -// pSelectedOnly: If passed True, only the selected nodes will bechnaged, if passed False all nodes in the control will be changed. -// pExcludeDisabled: Optiopnal. If passed True (the default value), disabled checkboxes won't be changed, if passed False disabled checkboxes will be altered too. - -var - lItem : PVirtualNode; -begin - With Self do begin - Screen.Cursor := crHourGlass; - BeginUpdate; - try - if pSelectedOnly then - lItem := GetFirstSelected - else - lItem := GetFirst; - //for i:=0 to List.Items.Count-1 do begin - while Assigned(lItem) do begin - if not pExcludeDisabled or not CheckState[lItem].IsDisabled() then - CheckState[lItem] := aCheckState; - if pSelectedOnly then - lItem := GetNextSelected(lItem) - else - lItem := GetNext(lItem); - end;//while - finally - Screen.Cursor := crDefault; - EndUpdate; - end;//try..finally - end;//With -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetCheckType(Node: PVirtualNode; Value: TCheckType); - -begin - if (Node.CheckType <> Value) and not (toReadOnly in FOptions.MiscOptions) then - begin - Node.CheckType := Value; - if (Value <> ctTriStateCheckBox) and (Node.CheckState in [csMixedNormal, csMixedPressed]) then - Node.CheckState := csUncheckedNormal;// reset check state if it doesn't fit the new check type - // For check boxes with tri-state check box parents we have to initialize differently. - if (toAutoTriStateTracking in FOptions.AutoOptions) and (Value in [ctCheckBox, ctTriStateCheckBox]) and - (Node.Parent <> FRoot) then - begin - if not (vsInitialized in Node.Parent.States) then - InitNode(Node.Parent); - if (Node.Parent.CheckType = ctTriStateCheckBox) then begin - if (GetCheckState(Node.Parent) in [csUncheckedNormal, csUncheckedDisabled]) then - CheckState[Node] := csUncheckedNormal - else if (GetCheckState(Node.Parent) in [csCheckedNormal, csCheckedDisabled]) then - CheckState[Node] := csCheckedNormal; - end;//if - end;//if - InvalidateNode(Node); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetChildCount(Node: PVirtualNode; NewChildCount: Cardinal); - -// Changes a node's child structure to accomodate the new child count. This is used to add or delete -// child nodes to/from the end of the node's child list. To insert or delete a specific node a separate -// routine is used. - -var - Remaining: Cardinal; - Index: Cardinal; - Child: PVirtualNode; - Count: Integer; - NewHeight: Integer; -begin - if not (toReadOnly in FOptions.MiscOptions) then - begin - if Node = nil then - Node := FRoot; - - Assert(GetCurrentThreadId = MainThreadId, 'UI controls may only be changed in UI thread.'); - if NewChildCount = 0 then - DeleteChildren(Node) - else - begin - // If nothing changed then do nothing. - if NewChildCount <> Node.ChildCount then - begin - InterruptValidation; - - if NewChildCount > Node.ChildCount then - begin - Remaining := NewChildCount - Node.ChildCount; - Count := Remaining; - NewHeight := Node.TotalHeight; - - // New nodes to add. - if Assigned(Node.LastChild) then - Index := Node.LastChild.Index + 1 - else - begin - Index := 0; - Include(Node.States, vsHasChildren); - end; - Node.States := Node.States - [vsAllChildrenHidden, vsHeightMeasured]; - if (vsExpanded in Node.States) and FullyVisible[Node] then - Inc(FVisibleCount, Count); // Do this before a possible init of the sub-nodes in DoMeasureItem() - - // New nodes are by default always visible, so we don't need to check the visibility. - while Remaining > 0 do - begin - Child := MakeNewNode; - Child.Index := Index; - Child.PrevSibling := Node.LastChild; - if Assigned(Node.LastChild) then - Node.LastChild.NextSibling := Child; - Child.Parent := Node; - Node.LastChild := Child; - if Node.FirstChild = nil then - Node.FirstChild := Child; - Dec(Remaining); - Inc(Index); - - if (toVariableNodeHeight in FOptions.MiscOptions) then - GetNodeHeight(Child); - Inc(NewHeight, Child.TotalHeight); - end; - - if vsExpanded in Node.States then - AdjustTotalHeight(Node, NewHeight, False); - - AdjustTotalCount(Node, Count, True); - Node.ChildCount := NewChildCount; - if (FUpdateCount = 0) and (toAutoSort in FOptions.AutoOptions) and (FHeader.SortColumn > InvalidColumn) then - Sort(Node, FHeader.SortColumn, FHeader.SortDirection, True); - - InvalidateCache; - end//if NewChildCount > Node.ChildCount - else - begin - // Nodes have to be deleted. - Remaining := Node.ChildCount - NewChildCount; - while Remaining > 0 do - begin - DeleteNode(Node.LastChild); - Dec(Remaining); - end; - end; - - if FUpdateCount = 0 then - begin - ValidateCache; - UpdateScrollBars(True); - Invalidate; - end; - - if Node = FRoot then - StructureChange(nil, crChildAdded) - else - StructureChange(Node, crChildAdded); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetClipboardFormats(const Value: TClipboardFormats); - -var - I: Integer; - -begin - // Add string by string instead doing an Assign or AddStrings because the list may return -1 for - // invalid entries which cause trouble for the standard implementation. - FClipboardFormats.Clear; - for I := 0 to Value.Count - 1 do - FClipboardFormats.Add(Value[I]); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetColors(const Value: TVTColors); - -begin - FColors.Assign(Value); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetCheckImageKind(Value: TCheckImageKind); -begin - if (Value < Low(Value)) or (Value> High(Value)) then - Value := ckSystemDefault; - // property is deprecated. See issue #622 - if FCheckImageKind <> Value then - begin - if FCheckImageKind = ckSystemDefault then - FreeAndNil(FCheckImages); - FCheckImageKind := Value; - if Value = ckCustom then - FCheckImages := FCustomCheckImages - else if HandleAllocated then - FCheckImages := CreateSystemImageSet(Self); - if HandleAllocated and (FUpdateCount = 0) and not (csLoading in ComponentState) then - InvalidateRect(Handle, nil, False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetCustomCheckImages(const Value: TCustomImageList); - -begin - if FCustomCheckImages <> Value then - begin - if Assigned(FCustomCheckImages) then - begin - FCustomCheckImages.UnRegisterChanges(FCustomCheckChangeLink); - FCustomCheckImages.RemoveFreeNotification(Self); - // Reset the internal check image list reference too, if necessary. - if FCheckImages = FCustomCheckImages then - FCheckImages := nil; - end; - FCustomCheckImages := Value; - if Assigned(FCustomCheckImages) then - begin - // If custom check images are assigned, we switch the property CheckImageKind to ckCustom so that they are actually used - CheckImageKind := ckCustom; - FCustomCheckImages.RegisterChanges(FCustomCheckChangeLink); - FCustomCheckImages.FreeNotification(Self); - end - else - CheckImageKind := ckSystemDefault; - if not (csLoading in ComponentState) then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetDefaultNodeHeight(Value: Cardinal); - -begin - if Value = 0 then - Value := 18; - if FDefaultNodeHeight <> Value then - begin - Inc(Integer(FRoot.TotalHeight), Integer(Value) - Integer(FDefaultNodeHeight)); - Inc(SmallInt(FRoot.NodeHeight), Integer(Value) - Integer(FDefaultNodeHeight)); - FDefaultNodeHeight := Value; - InvalidateCache; - if (FUpdateCount = 0) and HandleAllocated and not (csLoading in ComponentState) then - begin - ValidateCache; - UpdateScrollBars(True); - ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions, True); - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetDisabled(Node: PVirtualNode; Value: Boolean); - -begin - if Assigned(Node) and (Value xor (vsDisabled in Node.States)) then - begin - if Value then - Include(Node.States, vsDisabled) - else - Exclude(Node.States, vsDisabled); - - if FUpdateCount = 0 then - InvalidateNode(Node); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetDoubleBuffered(const Value: Boolean); -begin - // empty by intention, we do our own buffering -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetDoubleBuffered: Boolean; -begin - Result := True; // we do our own buffering -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetEmptyListMessage(const Value: string); - -begin - if Value <> EmptyListMessage then - begin - FEmptyListMessage := Value; - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetExpanded(Node: PVirtualNode; Value: Boolean); - -begin - if Assigned(Node) and (Node <> FRoot) and (Value xor (vsExpanded in Node.States)) then - ToggleNode(Node); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetFocusedColumn(Value: TColumnIndex); - -begin - if (FFocusedColumn <> Value) and - DoFocusChanging(FFocusedNode, FFocusedNode, FFocusedColumn, Value) then - begin - CancelEditNode; - InvalidateColumn(FFocusedColumn); - InvalidateColumn(Value); - FFocusedColumn := Value; - if Assigned(FFocusedNode) and not (toDisableAutoscrollOnFocus in FOptions.AutoOptions) then - begin - if ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions, True) then - InvalidateNode(FFocusedNode); - end; - - if Assigned(FDropTargetNode) then - InvalidateNode(FDropTargetNode); - - DoFocusChange(FFocusedNode, FFocusedColumn); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetFocusedNode(Value: PVirtualNode); - -var - WasDifferent: Boolean; - -begin - WasDifferent := Value <> FFocusedNode; - DoFocusNode(Value, True); - // Do change event only if there was actually a change. - if WasDifferent and (FFocusedNode = Value) then - DoFocusChange(FFocusedNode, FFocusedColumn); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetFullyVisible(Node: PVirtualNode; Value: Boolean); - -// This method ensures that a node is visible and all its parent nodes are expanded and also visible -// if Value is True. Otherwise the visibility flag of the node is reset but the expand state -// of the parent nodes stays untouched. - -begin - Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter'); - - IsVisible[Node] := Value; - if Value then - begin - repeat - Node := Node.Parent; - if Node = FRoot then - Break; - if not (vsExpanded in Node.States) then - ToggleNode(Node); - if not (vsVisible in Node.States) then - IsVisible[Node] := True; - until False; - end; - ScrollIntoView(Node, False); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetHasChildren(Node: PVirtualNode; Value: Boolean); - -begin - if Assigned(Node) and not (toReadOnly in FOptions.MiscOptions) then - begin - if Value then - Include(Node.States, vsHasChildren) - else - begin - Exclude(Node.States, vsHasChildren); - DeleteChildren(Node); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetHeader(const Value: TVTHeader); - -begin - FHeader.Assign(Value); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetHotNode(Value: PVirtualNode); - -begin - FCurrentHotNode := Value; -end; - - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetFiltered(Node: PVirtualNode; Value: Boolean); - -// Sets the 'filtered' flag of the given node according to Value and updates all dependent states. - -var - NeedUpdate: Boolean; - -begin - Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); - - // Initialize the node if necessary as this might change the filtered state. - if not (vsInitialized in Node.States) then - InitNode(Node); - - if Value <> (vsFiltered in Node.States) then - begin - InterruptValidation; - NeedUpdate := False; - if Value then - begin - Include(Node.States, vsFiltered); - if not (toShowFilteredNodes in FOptions.PaintOptions) then - begin - if (vsInitializing in Node.States) and not (vsHasChildren in Node.States) then - AdjustTotalHeight(Node, 0, False) - else - AdjustTotalHeight(Node, -Integer(NodeHeight[Node]), True); - if FullyVisible[Node] then - begin - Dec(FVisibleCount); - NeedUpdate := True; - end; - if FocusedNode = Node then - FocusedNode := nil; - end; - - if FUpdateCount = 0 then - DetermineHiddenChildrenFlag(Node.Parent) - else - Include(FStates, tsUpdateHiddenChildrenNeeded); - end - else - begin - Exclude(Node.States, vsFiltered); - if not (toShowFilteredNodes in FOptions.PaintOptions) then - begin - AdjustTotalHeight(Node, Integer(NodeHeight[Node]), True); - if FullyVisible[Node] then - begin - Inc(FVisibleCount); - NeedUpdate := True; - end; - end; - - if vsVisible in Node.States then - // Update the hidden children flag of the parent. - // Since this node is now visible we simply have to remove the flag. - Exclude(Node.Parent.States, vsAllChildrenHidden); - end; - - InvalidateCache; - if NeedUpdate and (FUpdateCount = 0) then - begin - ValidateCache; - UpdateScrollBars(True); - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- -procedure TBaseVirtualTree.SetImages(const Value: TCustomImageList); - -begin - if FImages <> Value then - begin - if Assigned(FImages) then - begin - FImages.UnRegisterChanges(FImageChangeLink); - FImages.RemoveFreeNotification(Self); - end; - FImages := Value; - if Assigned(FImages) then - begin - FImages.RegisterChanges(FImageChangeLink); - FImages.FreeNotification(Self); - end; - if not (csLoading in ComponentState) then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetIndent(Value: Cardinal); - -begin - if FIndent <> Value then - begin - FIndent := Value; - if not (csLoading in ComponentState) and (FUpdateCount = 0) and HandleAllocated then - begin - UpdateScrollBars(True); - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetLineMode(const Value: TVTLineMode); - -begin - if FLineMode <> Value then - begin - FLineMode := Value; - if HandleAllocated and not (csLoading in ComponentState) then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetLineStyle(const Value: TVTLineStyle); - -begin - if FLineStyle <> Value then - begin - FLineStyle := Value; - if not (csLoading in ComponentState) then - begin - PrepareBitmaps(False, True); - if HandleAllocated then - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetMargin(Value: Integer); - -begin - if FMargin <> Value then - begin - FMargin := Value; - if HandleAllocated and not (csLoading in ComponentState) then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetMultiline(Node: PVirtualNode; const Value: Boolean); - -begin - if Assigned(Node) and (Node <> FRoot) then - if Value <> (vsMultiline in Node.States) then - begin - if Value then - Include(Node.States, vsMultiline) - else - Exclude(Node.States, vsMultiline); - - if FUpdateCount = 0 then - InvalidateNode(Node); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetNodeAlignment(const Value: TVTNodeAlignment); - -begin - if FNodeAlignment <> Value then - begin - FNodeAlignment := Value; - if HandleAllocated and not (csReading in ComponentState) then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetNodeData(pNode: PVirtualNode; pUserData: Pointer); - - // Can be used to set user data of a PVirtualNode with the size of a pointer, useful for setting - // A pointer to a record or a reference to a class instance. - -var - NodeData: PPointer; -begin - // Check if there is initial user data and there is also enough user data space allocated. - Assert(FNodeDataSize >= SizeOf(Pointer), Self.Classname + ': Cannot set initial user data because there is not enough user data space allocated.'); - NodeData := PPointer(@pNode.Data); - NodeData^ := pUserData; - Include(pNode.States, vsOnFreeNodeCallRequired); -end; - -procedure TBaseVirtualTree.SetNodeData(pNode: PVirtualNode; pUserData: T); - - // Can be used to set user data of a PVirtualNode to a class instance. - -begin - pNode.SetData(pUserData); -end; - -procedure TBaseVirtualTree.SetNodeData(pNode: PVirtualNode; const pUserData: IInterface); - - // Can be used to set user data of a PVirtualNode to a class instance, - // will take care about reference counting. - -begin - pNode.SetData(pUserData); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetNodeDataSize(Value: Integer); - -var - LastRootCount: Cardinal; - -begin - if Value < -1 then - Value := -1; - if FNodeDataSize <> Value then - begin - FNodeDataSize := Value; - if not (csLoading in ComponentState) and not (csDesigning in ComponentState) then - begin - LastRootCount := FRoot.ChildCount; - Clear; - SetRootNodeCount(LastRootCount); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetNodeHeight(Node: PVirtualNode; Value: Cardinal); - -var - Difference: Integer; - -begin - Assert(Assigned(Node), 'SetNodeHeight() cannot be called with Node = nil'); - Assert((Node <> FRoot), 'SetNodeHeight() cannot be called for the root node FRoot'); - if (Node.NodeHeight <> Value) then - begin - Difference := Integer(Value) - Integer(Node.NodeHeight); - Node.NodeHeight := Value; - - // If the node is effectively filtered out, nothing else has to be done, as it is not visible anyway. - if not IsEffectivelyFiltered[Node] then - begin - AdjustTotalHeight(Node, Difference, True); - - // If an edit operation is currently active then update the editors boundaries as well. - UpdateEditBounds; - - InvalidateCache; - // Stay away from touching the node cache while it is being validated. - if not (tsValidating in FStates) and FullyVisible[Node] then - begin - if (FUpdateCount = 0) and ([tsPainting, tsSizing] * FStates = []) then - begin - ValidateCache; - InvalidateToBottom(Node); - UpdateScrollBars(True); - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetNodeParent(Node: PVirtualNode; const Value: PVirtualNode); - -begin - if Assigned(Node) and Assigned(Value) and (Node.Parent <> Value) then - MoveTo(Node, Value, amAddChildLast, False); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetOffsetX(const Value: Integer); - -begin - DoSetOffsetXY(Point(Value, FOffsetY), DefaultScrollUpdateFlags); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetOffsetXY(const Value: TPoint); - -begin - DoSetOffsetXY(Value, DefaultScrollUpdateFlags); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetOffsetY(const Value: Integer); - -begin - DoSetOffsetXY(Point(FOffsetX, Value), DefaultScrollUpdateFlags); -end; - -procedure TBaseVirtualTree.SetOnPrepareButtonImages(const Value: TVTPrepareButtonImagesEvent); -begin - FOnPrepareButtonImages := Value; - PrepareBitmaps(True, False); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetOptions(const Value: TCustomVirtualTreeOptions); - -begin - FOptions.Assign(Value); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetRangeX(value: Cardinal); -begin - FRangeX := value; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetRootNodeCount(Value: Cardinal); - -begin - // Don't set the root node count until all other properties (in particular the OnInitNode event) have been set. - if csLoading in ComponentState then - begin - FRoot.ChildCount := Value; - DoStateChange([tsNeedRootCountUpdate]); - end - else - if FRoot.ChildCount <> Value then - begin - BeginUpdate; - InterruptValidation; - SetChildCount(FRoot, Value); - EndUpdate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetScrollBarOptions(Value: TScrollBarOptions); - -begin - FScrollBarOptions.Assign(Value); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetSearchOption(const Value: TVTIncrementalSearch); - -begin - if FIncrementalSearch <> Value then - begin - FIncrementalSearch := Value; - if FIncrementalSearch = isNone then - begin - StopTimer(SearchTimer); - FSearchBuffer := ''; - FLastSearchNode := nil; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetSelected(Node: PVirtualNode; Value: Boolean); - -begin - if not FSelectionLocked and Assigned(Node) and (Node <> FRoot) and (Value xor (vsSelected in Node.States)) then - begin - if Value then - begin - if FSelectionCount = 0 then - FRangeAnchor := Node - else begin - if not (toMultiSelect in FOptions.SelectionOptions) then - ClearSelection; - if FRangeAnchor = nil then - FRangeAnchor := Node; - end; - - AddToSelection(Node, True); - - if not (toMultiSelect in FOptions.SelectionOptions) then - FocusedNode := GetFirstSelected; // if only one node can be selected, make sure the focused node changes with the selected node - // Make sure there is a valid column selected (if there are columns at all). - if ((FFocusedColumn < 0) or not (coVisible in FHeader.Columns[FFocusedColumn].Options)) and - (FHeader.MainColumn > NoColumn) then - if ([coVisible, coAllowFocus] * FHeader.Columns[FHeader.MainColumn].Options = [coVisible, coAllowFocus]) then - FFocusedColumn := FHeader.MainColumn - else - FFocusedColumn := FHeader.Columns.GetFirstVisibleColumn(True); - end - else - begin - RemoveFromSelection(Node); - if FSelectionCount = 0 then - ResetRangeAnchor; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetSelectionCurveRadius(const Value: Cardinal); - -begin - if FSelectionCurveRadius <> Value then - begin - FSelectionCurveRadius := Value; - if HandleAllocated and not (csLoading in ComponentState) then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetStateImages(const Value: TCustomImageList); - -begin - if FStateImages <> Value then - begin - if Assigned(FStateImages) then - begin - FStateImages.UnRegisterChanges(FStateChangeLink); - FStateImages.RemoveFreeNotification(Self); - end; - FStateImages := Value; - if Assigned(FStateImages) then - begin - FStateImages.RegisterChanges(FStateChangeLink); - FStateImages.FreeNotification(Self); - end; - if HandleAllocated and not (csLoading in ComponentState) then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetTextMargin(Value: Integer); - -begin - if FTextMargin <> Value then - begin - FTextMargin := Value; - if not (csLoading in ComponentState) then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetTopNode(Node: PVirtualNode); - -var - R: TRect; - Run: PVirtualNode; - -begin - if Assigned(Node) then - begin - // make sure all parents of the node are expanded - Run := Node.Parent; - while Run <> FRoot do - begin - if not (vsExpanded in Run.States) then - ToggleNode(Run); - Run := Run.Parent; - end; - R := GetDisplayRect(Node, FHeader.MainColumn, True); - SetOffsetY(FOffsetY - R.Top); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetUpdateState(Updating: Boolean); - -begin - // The check for visibility is necessary otherwise the tree is automatically shown when - // updating is allowed. As this happens internally the VCL does not get notified and - // still assumes the control is hidden. This results in weird "cannot focus invisible control" errors. - if Visible and HandleAllocated and (FUpdateCount = 0) then - SendMessage(Handle, WM_SETREDRAW, Ord(not Updating), 0); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetVerticalAlignment(Node: PVirtualNode; Value: Byte); - -begin - if Value > 100 then - Value := 100; - if Node.Align <> Value then - begin - Node.Align := Value; - if FullyVisible[Node] and not IsEffectivelyFiltered[Node] then - InvalidateNode(Node); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetVisible(Node: PVirtualNode; Value: Boolean); - -// Sets the visibility style of the given node according to Value. - -var - NeedUpdate: Boolean; - -begin - Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); - - if Value <> (vsVisible in Node.States) then - begin - InterruptValidation; - NeedUpdate := False; - if Value then - begin - Include(Node.States, vsVisible); - if vsExpanded in Node.Parent.States then - AdjustTotalHeight(Node.Parent, Node.TotalHeight, True); - if VisiblePath[Node] then - begin - Inc(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1))); - NeedUpdate := True; - end; - - // Update the hidden children flag of the parent. - // Since this node is now visible we simply have to remove the flag. - if not IsEffectivelyFiltered[Node] then - Exclude(Node.Parent.States, vsAllChildrenHidden); - end - else - begin - if vsExpanded in Node.Parent.States then - AdjustTotalHeight(Node.Parent, -Integer(Node.TotalHeight), True); - if VisiblePath[Node] then - begin - Dec(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1))); - NeedUpdate := True; - end; - Exclude(Node.States, vsVisible); - - if FUpdateCount = 0 then - DetermineHiddenChildrenFlag(Node.Parent) - else - Include(FStates, tsUpdateHiddenChildrenNeeded); - end; - - InvalidateCache; - if NeedUpdate and (FUpdateCount = 0) then - begin - ValidateCache; - UpdateScrollBars(True); - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetVisiblePath(Node: PVirtualNode; Value: Boolean); - -// If Value is True then all parent nodes of Node are expanded. - -begin - Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); - - if Value then - begin - repeat - Node := Node.Parent; - if Node = FRoot then - Break; - if not (vsExpanded in Node.States) then - ToggleNode(Node); - until False; - end; -end; - -// ---------------------------------------------------------------------------------------------------------------------- -procedure TBaseVirtualTree.PrepareBackGroundPicture(Source: TPicture; - DrawBitmap: TBitmap; DrawBitmapWidth: Integer; DrawBitMapHeight: Integer; ABkgcolor: TColor); -const - DST = $00AA0029; // Ternary Raster Operation - Destination unchanged - - // fill background will work for transparent images and - // will not disturb non-transparent ones - procedure FillDrawBitmapWithBackGroundColor; - begin - DrawBitmap.Canvas.Brush.Color := ABkgcolor; - DrawBitmap.Canvas.FillRect(Rect(0, 0, DrawBitmap.Width, DrawBitmap.Height)); - end; - -begin - DrawBitmap.SetSize(DrawBitmapWidth, DrawBitMapHeight); - - if (Source.Graphic is TBitmap) and - (FBackGroundImageTransparent or Source.Bitmap.TRANSPARENT) - then - begin - FillDrawBitmapWithBackGroundColor; - MaskBlt(DrawBitmap.Canvas.Handle, 0, 0, Source.Width, Source.Height, - Source.Bitmap.Canvas.Handle, 0, 0, Source.Bitmap.MaskHandle, 0, 0, - MakeROP4(DST, SRCCOPY)); - end - else - begin - // Similar to TImage's Transparent property behavior, we don't want - // to draw transparent if the following flag is OFF. - if FBackGroundImageTransparent then - FillDrawBitmapWithBackGroundColor; - DrawBitmap.Canvas.Draw(0, 0, Source.Graphic); - end -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.StaticBackground(Source: TPicture; Target: TCanvas; OffsetPosition: TPoint; R: TRect; aBkgColor: TColor); - -// Draws the given source graphic so that it stays static in the given rectangle which is relative to the target bitmap. -// The graphic is aligned so that it always starts at the upper left corner of the target canvas. -// Offset gives the position of the target window as a possible superordinated surface. - -const - DST = $00AA0029; // Ternary Raster Operation - Destination unchanged - -var - PicRect: TRect; - AreaRect: TRect; - DrawRect: TRect; - DrawBitmap: TBitmap; -begin - DrawBitmap := TBitmap.Create; - try - // clear background - Target.Brush.Color := aBkgColor; - Target.FillRect(R); - - // Picture rect in relation to client viewscreen. - PicRect := Rect(FBackgroundOffsetX, FBackgroundOffsetY, FBackgroundOffsetX + Source.Width, FBackgroundOffsetY + Source.Height); - - // Area to be draw in relation to client viewscreen. - AreaRect := Rect(OffsetPosition.X + R.Left, OffsetPosition.Y + R.Top, OffsetPosition.X + R.Right, OffsetPosition.Y + R.Bottom); - - // If picture falls in AreaRect, return intersection (DrawRect). - if IntersectRect(DrawRect, PicRect, AreaRect) then - begin - PrepareBackGroundPicture(Source, DrawBitmap, Source.Width, Source.Height, aBkgColor); - // copy image to destination - BitBlt(Target.Handle, DrawRect.Left - OffsetPosition.X, DrawRect.Top - OffsetPosition.Y, (DrawRect.Right - OffsetPosition.X) - (DrawRect.Left - OffsetPosition.X), - (DrawRect.Bottom - OffsetPosition.Y) - (DrawRect.Top - OffsetPosition.Y) + R.Top, DrawBitmap.Canvas.Handle, DrawRect.Left - PicRect.Left, DrawRect.Top - PicRect.Top, - SRCCOPY); - end; - finally - DrawBitmap.Free; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.StopTimer(ID: Integer); - -begin - if HandleAllocated then - KillTimer(Handle, ID); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetWindowTheme(const Theme: string); - -begin - FChangingTheme := True; - Winapi.UxTheme.SetWindowTheme(Handle, PWideChar(Theme), nil); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -//used by TCustomVirtualTreeOptions -procedure TBaseVirtualTree.SetVisibleCount(value : Cardinal); -begin - FVisibleCount := value; -end; - - -procedure TBaseVirtualTree.TileBackground(Source: TPicture; Target: TCanvas; Offset: TPoint; R: TRect; aBkgColor: TColor); - -// Draws the given source graphic so that it tiles into the given rectangle which is relative to the target bitmap. -// The graphic is aligned so that it always starts at the upper left corner of the target canvas. -// Offset gives the position of the target window in an possible superordinated surface. - -var - SourceX, - SourceY, - TargetX, - DeltaY: Integer; - DrawBitmap: TBitmap; -begin - DrawBitmap := TBitmap.Create; - try - PrepareBackGroundPicture(Source, DrawBitmap, Source.Width, Source.Height, aBkgColor); - with Target do - begin - SourceY := (R.Top + Offset.Y + FBackgroundOffsetY) mod Source.Height; - // Always wrap the source coordinates into positive range. - if SourceY < 0 then - SourceY := Source.Height + SourceY; - - // Tile image vertically until target rect is filled. - while R.Top < R.Bottom do - begin - SourceX := (R.Left + Offset.X + FBackgroundOffsetX) mod Source.Width; - // always wrap the source coordinates into positive range - if SourceX < 0 then - SourceX := Source.Width + SourceX; - - TargetX := R.Left; - // height of strip to draw - DeltaY := Min(R.Bottom - R.Top, Source.Height - SourceY); - - // tile the image horizontally - while TargetX < R.Right do - begin - BitBlt(Handle, TargetX, R.Top, Min(R.Right - TargetX, Source.Width - SourceX), DeltaY, - DrawBitmap.Canvas.Handle, SourceX, SourceY, SRCCOPY); - Inc(TargetX, Source.Width - SourceX); - SourceX := 0; - end; - Inc(R.Top, Source.Height - SourceY); - SourceY := 0; - end; - end; - finally - DrawBitmap.Free; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ToggleCallback(Step, StepSize: Integer; Data: Pointer): Boolean; - -var - Column: TColumnIndex; - Run: TRect; - SecondaryStepSize: Integer; - - //--------------- local functions ------------------------------------------- - - procedure EraseLine; - - var - LocalBrush: HBRUSH; - - begin - with TToggleAnimationData(Data^), FHeader.Columns do - begin - // Iterate through all columns and erase background in their local color. - // LocalBrush is a brush in the color of the particular column. - Column := GetFirstVisibleColumn; - while (Column > InvalidColumn) and (Run.Left < ClientWidth) do - begin - GetColumnBounds(Column, Run.Left, Run.Right); - if coParentColor in Items[Column].Options then - FillRect(DC, Run, Brush) - else - begin - if VclStyleEnabled then - LocalBrush := CreateSolidBrush(ColorToRGB(FColors.BackGroundColor)) - else - LocalBrush := CreateSolidBrush(ColorToRGB(Items[Column].Color)); - FillRect(DC, Run, LocalBrush); - DeleteObject(LocalBrush); - end; - Column := GetNextVisibleColumn(Column); - end; - end; - end; - - //--------------------------------------------------------------------------- - - procedure DoScrollUp(DC: HDC; Brush: HBRUSH; Area: TRect; Steps: Integer); - - begin - ScrollDC(DC, 0, -Steps, Area, Area, 0, nil); - - if Step = 0 then - if not FHeader.UseColumns then - FillRect(DC, Rect(Area.Left, Area.Bottom - Steps - 1, Area.Right, Area.Bottom), Brush) - else - begin - Run := Rect(Area.Left, Area.Bottom - Steps - 1, Area.Right, Area.Bottom); - EraseLine; - end; - end; - - //--------------------------------------------------------------------------- - - procedure DoScrollDown(DC: HDC; Brush: HBRUSH; Area: TRect; Steps: Integer); - - begin - ScrollDC(DC, 0, Steps, Area, Area, 0, nil); - - if Step = 0 then - if not FHeader.UseColumns then - FillRect(DC, Rect(Area.Left, Area.Top, Area.Right, Area.Top + Steps + 1), Brush) - else - begin - Run := Rect(Area.Left, Area.Top, Area.Right, Area.Top + Steps + 1); - EraseLine; - end; - end; - - //--------------- end local functions --------------------------------------- - -begin - Result := True; - if StepSize > 0 then - begin - SecondaryStepSize := 0; - with TToggleAnimationData(Data^) do - begin - if Mode1 <> tamNoScroll then - begin - if Mode1 = tamScrollUp then - DoScrollUp(DC, Brush, R1, StepSize) - else - DoScrollDown(DC, Brush, R1, StepSize); - - if (Mode2 <> tamNoScroll) and (ScaleFactor > 0) then - begin - // As this routine is able to scroll two independent areas at once, the missing StepSize is - // computed in that case. To ensure the maximal accuracy the rounding error is accumulated. - SecondaryStepSize := Round((StepSize + MissedSteps) * ScaleFactor); - MissedSteps := MissedSteps + StepSize * ScaleFactor - SecondaryStepSize; - end; - end - else - SecondaryStepSize := StepSize; - - if Mode2 <> tamNoScroll then - if Mode2 = tamScrollUp then - DoScrollUp(DC, Brush, R2, SecondaryStepSize) - else - DoScrollDown(DC, Brush, R2, SecondaryStepSize); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMColorChange(var Message: TMessage); - -begin - if not (csLoading in ComponentState) then - begin - PrepareBitmaps(True, False); - if HandleAllocated then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMCtl3DChanged(var Message: TMessage); - -begin - inherited; - if FBorderStyle = bsSingle then - RecreateWnd; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMBiDiModeChanged(var Message: TMessage); - -begin - inherited; - - if UseRightToLeftAlignment then - FEffectiveOffsetX := Integer(FRangeX) - ClientWidth + FOffsetX - else - FEffectiveOffsetX := -FOffsetX; - if FEffectiveOffsetX < 0 then - FEffectiveOffsetX := 0; - - if toAutoBidiColumnOrdering in FOptions.AutoOptions then - FHeader.Columns.ReorderColumns(UseRightToLeftAlignment); - FHeader.Invalidate(nil); -end; - -procedure TBaseVirtualTree.CMBorderChanged(var Message: TMessage); -begin - inherited; - if VclStyleEnabled and (seBorder in StyleElements) then - RecreateWnd; -end; - -procedure TBaseVirtualTree.CMParentDoubleBufferedChange(var Message: TMessage); -begin - // empty by intention, we do our own buffering -end; - -procedure TBaseVirtualTree.CMStyleChanged(var Message: TMessage); -begin - VclStyleChanged; - RecreateWnd; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMDenySubclassing(var Message: TMessage); - -// If a Windows XP Theme Manager component is used in the application it will try to subclass all controls which do not -// explicitly deny this. Virtual Treeview knows how to handle XP themes so it does not need subclassing. - -begin - Message.Result := 1; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMDrag(var Message: TCMDrag); - -var - S: TObject; - ShiftState: Integer; - P: TPoint; - Formats: TFormatArray; - Effect: Integer; - -begin - with Message, DragRec^ do - begin - S := Source; - Formats := nil; - - // Let the ancestor handle dock operations. - if S is TDragDockObject then - inherited - else - begin - // We need an extra check for the control drag object as there might be other objects not derived from - // this class (e.g. TActionDragObject). - if not (tsUserDragObject in FStates) and (S is TBaseDragControlObject) then - S := (S as TBaseDragControlObject).Control; - case DragMessage of - dmDragEnter, dmDragLeave, dmDragMove: - begin - if DragMessage = dmDragEnter then - DoStateChange([tsVCLDragging]); - if DragMessage = dmDragLeave then - DoStateChange([tsVCLDragFinished], [tsVCLDragging]); - - if DragMessage = dmDragMove then - with ScreenToClient(Pos) do - DoAutoScroll(X, Y); - - ShiftState := 0; - // Alt key will be queried by the KeysToShiftState function in DragOver. - if GetKeyState(VK_SHIFT) < 0 then - ShiftState := ShiftState or MK_SHIFT; - if GetKeyState(VK_CONTROL) < 0 then - ShiftState := ShiftState or MK_CONTROL; - - // Allowed drop effects are simulated for VCL dd. - Effect := DROPEFFECT_MOVE or DROPEFFECT_COPY; - DragOver(S, ShiftState, TDragState(DragMessage), Pos, Effect); - FLastVCLDragTarget := FDropTargetNode; - FVCLDragEffect := Effect; - if (DragMessage = dmDragLeave) and Assigned(FDropTargetNode) then - begin - InvalidateNode(FDropTargetNode); - FDropTargetNode := nil; - end; - Result := LRESULT(Effect); - end; - dmDragDrop: - begin - ShiftState := 0; - // Alt key will be queried by the KeysToShiftState function in DragOver - if GetKeyState(VK_SHIFT) < 0 then - ShiftState := ShiftState or MK_SHIFT; - if GetKeyState(VK_CONTROL) < 0 then - ShiftState := ShiftState or MK_CONTROL; - - // allowed drop effects are simulated for VCL dd, - // replace target node with cached node from other VCL dd messages - if Assigned(FDropTargetNode) then - InvalidateNode(FDropTargetNode); - FDropTargetNode := FLastVCLDragTarget; - P := Point(Pos.X, Pos.Y); - P := ScreenToClient(P); - try - DoDragDrop(S, nil, Formats, KeysToShiftState(ShiftState), P, FVCLDragEffect, FLastDropMode); - finally - if Assigned(FDropTargetNode) then - begin - InvalidateNode(FDropTargetNode); - FDropTargetNode := nil; - end; - end; - end; - dmFindTarget: - begin - Result := LRESULT(ControlAtPos(ScreenToClient(Pos), False)); - if Result = 0 then - Result := LRESULT(Self); - - // This is a reliable place to check whether VCL drag has - // really begun. - if tsVCLDragPending in FStates then - DoStateChange([tsVCLDragging], [tsVCLDragPending, tsEditPending, tsClearPending]); - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMEnabledChanged(var Message: TMessage); - -begin - inherited; - - // Need to invalidate the non-client area as well, since the header must be redrawn too. - if csDesigning in ComponentState then - RedrawWindow(Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMFontChanged(var Message: TMessage); - -var - HeaderMessage: TMessage; - -begin - inherited; - - if not (csLoading in ComponentState) then - begin - if HandleAllocated then begin - AutoScale(False); - Invalidate; - end - end; - - HeaderMessage.Msg := CM_PARENTFONTCHANGED; - HeaderMessage.WParam := 0; - HeaderMessage.LParam := 0; - HeaderMessage.Result := 0; - FHeader.HandleMessage(HeaderMessage); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMHintShow(var Message: TCMHintShow); - -// Determines hint message (tooltip) and out-of-hint rect. -// Note: A special handling is needed here because we cannot pass wide strings back to the caller. -// I had to introduce the hint data record anyway so we can use this to pass the hint string. -// We still need to set a dummy hint string in the message to make the VCL showing the hint window. - -var - NodeRect: TRect; - SpanColumn, - Dummy, - ColLeft, - ColRight: Integer; - HitInfo: THitInfo; - ShowOwnHint: Boolean; - IsFocusedOrEditing: Boolean; - ParentForm: TCustomForm; - BottomRightCellContentMargin: TPoint; - HintKind: TVTHintKind; -begin - with Message do - begin - Result := 1; - - if PtInRect(FLastHintRect, HintInfo.CursorPos) then - Exit; - - // Determine node for which to show hint/tooltip. - with HintInfo^ do - GetHitTestInfoAt(CursorPos.X, CursorPos.Y, True, HitInfo); - - // Make sure a hint is only shown if the tree or at least its parent form is active. - // Active editing is ok too as long as we don't want the hint for the current edit node. - if IsEditing then - IsFocusedOrEditing := HitInfo.HitNode <> FFocusedNode - else - begin - IsFocusedOrEditing := Focused; - ParentForm := GetParentForm(Self); - if Assigned(ParentForm) then - IsFocusedOrEditing := ParentForm.Focused or Application.Active; - end; - - if (GetCapture = 0) and ShowHint and not (Dragging or IsMouseSelecting) and ([tsScrolling] * FStates = []) and - (FHeader.States = []) and IsFocusedOrEditing then - begin - with HintInfo^ do - begin - Result := 0; - ShowOwnHint := False; - - //workaround for issue #291 - //it duplicates parts of the following code and code in TVirtualTreeHintWindow - HintStr := ''; - if FHeader.UseColumns and (hoShowHint in FHeader.Options) and FHeader.InHeader(CursorPos) then - begin - CursorRect := FHeaderRect; - // Convert the cursor rectangle into real client coordinates. - OffsetRect(CursorRect, 0, -Integer(FHeader.Height)); - HitInfo.HitColumn := FHeader.Columns.GetColumnAndBounds(CursorPos, CursorRect.Left, CursorRect.Right); - if (HitInfo.HitColumn > NoColumn) and not (csLButtonDown in ControlState) and - (FHeader.Columns[HitInfo.HitColumn].Hint <> '') then - HintStr := FHeader.Columns[HitInfo.HitColumn].Hint; - end - else - if HintMode = hmDefault then - HintStr := GetShortHint(Hint) - else - if Assigned(HitInfo.HitNode) and (HitInfo.HitColumn > InvalidColumn) then - begin - if HintMode = hmToolTip then - HintStr := DoGetNodeToolTip(HitInfo.HitNode, HitInfo.HitColumn, fHintData.LineBreakStyle) - else - HintStr := DoGetNodeHint(HitInfo.HitNode, HitInfo.HitColumn, fHintData.LineBreakStyle); - end; - - // First check whether there is a header hint to show. - if FHeader.UseColumns and (hoShowHint in FHeader.Options) and FHeader.InHeader(CursorPos) then - begin - CursorRect := FHeaderRect; - // Convert the cursor rectangle into real client coordinates. - OffsetRect(CursorRect, 0, -Integer(FHeader.Height)); - HitInfo.HitColumn := FHeader.Columns.GetColumnAndBounds(CursorPos, CursorRect.Left, CursorRect.Right); - // align the vertical hint position on the bottom bound of the header, but - // avoid overlapping of mouse cursor and hint - HintPos.Y := Max(HintPos.Y, ClientToScreen(Point(0, CursorRect.Bottom)).Y); - // Note: the test for the left mouse button in ControlState might cause problems whenever the VCL does not - // realize when the button is released. This, for instance, happens when doing OLE drag'n drop and - // cancel this with ESC. - if (HitInfo.HitColumn > NoColumn) and not (csLButtonDown in ControlState) then - begin - HintStr := FHeader.Columns[HitInfo.HitColumn].Hint; - if HintStr = '' then - with FHeader.Columns[HitInfo.HitColumn] do - begin - if (2 * FMargin + CaptionWidth + 1) >= Width then - HintStr := FCaptionText; - end; - if HintStr <> '' then - ShowOwnHint := True - else - Result := 1; - end - else - Result := 1; - end - else - begin - // Default mode is handled as would the tree be a usual VCL control (no own hint window necessary). - if FHintMode = hmDefault then - HintStr := GetShortHint(Hint) - else - begin - if Assigned(HitInfo.HitNode) and (HitInfo.HitColumn > InvalidColumn) then - begin - // An owner-draw tree should only display a hint when at least - // its OnGetHintSize event handler is assigned. - DoGetHintKind(HitInfo.HitNode, HitInfo.HitColumn, HintKind); - FHintData.HintRect := Rect(0, 0, 0, 0); - if (HintKind = vhkOwnerDraw) then - begin - DoGetHintSize(HitInfo.HitNode, HitInfo.HitColumn, FHintData.HintRect); - ShowOwnHint := not IsRectEmpty(FHintData.HintRect); - end - else - // For trees displaying text hints, a decision about showing the hint or not is based - // on the hint string (if it is empty then no hint is shown). - ShowOwnHint := True; - - if ShowOwnHint then - begin - if HitInfo.HitColumn > NoColumn then - begin - FHeader.Columns.GetColumnBounds(HitInfo.HitColumn, ColLeft, ColRight); - // The right column border might be extended if column spanning is enabled. - if toAutoSpanColumns in FOptions.AutoOptions then - begin - SpanColumn := HitInfo.HitColumn; - repeat - Dummy := FHeader.Columns.GetNextVisibleColumn(SpanColumn); - if (Dummy = InvalidColumn) or not ColumnIsEmpty(HitInfo.HitNode, Dummy) then - Break; - SpanColumn := Dummy; - until False; - if SpanColumn <> HitInfo.HitColumn then - FHeader.Columns.GetColumnBounds(SpanColumn, Dummy, ColRight); - end; - end - else - begin - ColLeft := 0; - ColRight := ClientWidth; - end; - - if FHintMode <> hmTooltip then - begin - // Node specific hint text. - CursorRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, False); - CursorRect.Left := ColLeft; - CursorRect.Right := ColRight; - // Align the vertical hint position on the bottom bound of the node, but - // avoid overlapping of mouse cursor and hint. - HintPos.Y := Max(HintPos.Y, ClientToScreen(CursorRect.BottomRight).Y) + ScaledPixels(2); - end - else - begin - // Tool tip to show. This means the full caption of the node must be displayed. - if vsMultiline in HitInfo.HitNode.States then - begin - if hiOnItemLabel in HitInfo.HitPositions then - begin - ShowOwnHint := True; - NodeRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, True, False); - end - else - ShowOwnHint := False; - end - else - begin - NodeRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, True, True, True); - BottomRightCellContentMargin := DoGetCellContentMargin(HitInfo.HitNode, HitInfo.HitColumn, ccmtBottomRightOnly); - - ShowOwnHint := (HitInfo.HitColumn > InvalidColumn) and PtInRect(NodeRect, CursorPos) and - (CursorPos.X <= ColRight) and (CursorPos.X >= ColLeft) and - ( - // Show hint also if the node text is partially out of the client area. - // "ColRight - 1", since the right column border is not part of this cell. - ( (NodeRect.Right + BottomRightCellContentMargin.X) > Min(ColRight - 1, ClientWidth) ) or - (NodeRect.Left < Max(ColLeft, 0)) or - ( (NodeRect.Bottom + BottomRightCellContentMargin.Y) > ClientHeight ) or - (NodeRect.Top < 0) - ); - end; - - if ShowOwnHint then - begin - // Node specific hint text given will be retrieved when needed. - HintPos := ClientToScreen(Point(NodeRect.Left, NodeRect.Top)); - CursorRect := NodeRect; - end - else - // nothing to show - Result := 1; - end; - end - else - Result := 1; // Avoid hint if this is a draw tree returning an empty hint rectangle. - end - else - begin - // No node so fall back to control's hint (if indicated) or show nothing. - if FHintMode = hmHintAndDefault then - begin - HintStr := GetShortHint(Hint); - - // Fix for the problem: Default Hint once shown stayed even when - // node hint was to be displayed. The reason was that CursorRect - // was for the full client area. Now reducing it to remove the - // columns from it. - if BidiMode = bdLeftToRight then - CursorRect.Left := Header.Columns.TotalWidth - else - CursorRect.right := CursorRect.right - Header.Columns.TotalWidth; - - if Length(HintStr) = 0 then - Result := 1 - else - ShowOwnHint := True; - end - else - Result := 1; - end; - end; - end; - - // Set our own hint window class and prepare structure to be passed to the hint window. - if ShowOwnHint and (Result = 0) then - begin - HintWindowClass := GetHintWindowClass; - FHintData.HintText := HintStr; - FHintData.Tree := Self; - FHintData.Column := HitInfo.HitColumn; - FHintData.Node := HitInfo.HitNode; - FLastHintRect := CursorRect; - HintData := @FHintData; - end - else - FLastHintRect := Rect(0, 0, 0, 0); - end; - - // Remind that a hint is about to show. - if Result = 0 then - DoStateChange([tsHint]) - else - DoStateChange([], [tsHint]); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMHintShowPause(var Message: TCMHintShowPause); - -// Tells the application that the tree (and only the tree) does not want a delayed tool tip. -// Normal hints / header hints use the default delay (except for the first time). - - begin - if ShowHint and (FHintMode = hmToolTip) then - Message.Pause^ := 0; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMMouseEnter(var Message: TMessage); -begin - DoMouseEnter(); - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMMouseLeave(var Message: TMessage); - -var - LeaveStates: TVirtualTreeStates; - -begin - // Reset the last used hint rectangle in case the mouse enters the window within the bounds - if Assigned(FHintData.Tree) then - FHintData.Tree.FLastHintRect := Rect(0, 0, 0, 0); - - LeaveStates := [tsHint]; - if [tsWheelPanning, tsWheelScrolling] * FStates = [] then - begin - StopTimer(ScrollTimer); - LeaveStates := LeaveStates + [tsScrollPending, tsScrolling]; - end; - DoStateChange([], LeaveStates); - if Assigned(FCurrentHotNode) then - begin - DoHotChange(FCurrentHotNode, nil); - if (toHotTrack in FOptions.PaintOptions) or (toCheckSupport in FOptions.MiscOptions) then - InvalidateNode(FCurrentHotNode); - FCurrentHotNode := nil; - end; - - if Assigned(Header) then - begin - Header.FColumns.DownIndex := NoColumn; - Header.FColumns.HoverIndex := NoColumn; - Header.FColumns.CheckBoxHit := False; - end; - DoMouseLeave(); - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CMMouseWheel(var Message: TCMMouseWheel); - -var - ScrollAmount: Integer; - ScrollLines: DWORD; - RTLFactor: Integer; - WheelFactor: Double; - -begin - StopWheelPanning; - - inherited; - - if Message.Result = 0 then - begin - with Message do - begin - Result := 1; - WheelFactor := WheelDelta / WHEEL_DELTA; - if (FRangeY > Cardinal(ClientHeight)) and (not (ssShift in ShiftState)) then - begin - // Scroll vertically if there's something to scroll... - if ssCtrl in ShiftState then - ScrollAmount := Trunc(WheelFactor * ClientHeight) - else - begin - SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, @ScrollLines, 0); - if ScrollLines = WHEEL_PAGESCROLL then - ScrollAmount := Trunc(WheelFactor * ClientHeight) - else - ScrollAmount := Integer(Trunc(WheelFactor * ScrollLines * FDefaultNodeHeight)); - end; - SetOffsetY(FOffsetY + ScrollAmount); - end - else - begin - // ...else scroll horizontally if there's something to scroll. - if UseRightToLeftAlignment then - RTLFactor := -1 - else - RTLFactor := 1; - - if ssCtrl in ShiftState then - ScrollAmount := Trunc(WheelFactor * (ClientWidth - FHeader.Columns.GetVisibleFixedWidth)) - else - begin - SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, @ScrollLines, 0); - ScrollAmount := Trunc(WheelFactor * ScrollLines * FHeader.Columns.GetScrollWidth); - end; - SetOffsetX(FOffsetX + RTLFactor * ScrollAmount); - end; - end; - - end; - -end; - -//---------------------------------------------------------------------------------------------------------------------- -procedure TBaseVirtualTree.CMSysColorChange(var Message: TMessage); - -begin - inherited; - Message.Msg := WM_SYSCOLORCHANGE; - DefaultHandler(Message); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.TVMGetItem(var Message: TMessage); - -// Screen reader support function. The method returns information about a particular node. - -const - StateMask = TVIS_STATEIMAGEMASK or TVIS_OVERLAYMASK or TVIS_EXPANDED or TVIS_DROPHILITED or TVIS_CUT or - TVIS_SELECTED or TVIS_FOCUSED; - -var - Item: PTVItemEx; - Node: PVirtualNode; - Ghosted: Boolean; - ImageIndex: TImageIndex; - R: TRect; - Text: string; -begin - // We can only return valid data if a nodes reference is given. - Item := Pointer(Message.LParam); - Message.Result := Ord(((Item.mask and TVIF_HANDLE) <> 0) and Assigned(Item.hItem)); - if Message.Result = 1 then - begin - Node := Pointer(Item.hItem); - // Child count requested? - if (Item.mask and TVIF_CHILDREN) <> 0 then - Item.cChildren := Node.ChildCount; - // Index for normal image requested? - if (Item.mask and TVIF_IMAGE) <> 0 then - begin - ImageIndex := -1; - DoGetImageIndex(Node, ikNormal, -1, Ghosted, ImageIndex); - Item.iImage := ImageIndex; - end; - // Index for selected image requested? - if (Item.mask and TVIF_SELECTEDIMAGE) <> 0 then - begin - ImageIndex := -1; - DoGetImageIndex(Node, ikSelected, -1, Ghosted, ImageIndex); - Item.iSelectedImage := ImageIndex; - end; - // State info requested? - if (Item.mask and TVIF_STATE) <> 0 then - begin - // Everything, which is possible is returned. - Item.stateMask := StateMask; - Item.state := 0; - if Node = FFocusedNode then - Item.state := Item.state or TVIS_FOCUSED; - if vsSelected in Node.States then - Item.state := Item.state or TVIS_SELECTED; - if vsCutOrCopy in Node.States then - Item.state := Item.state or TVIS_CUT; - if Node = FDropTargetNode then - Item.state := Item.state or TVIS_DROPHILITED; - if vsExpanded in Node.States then - Item.state := Item.state or TVIS_EXPANDED; - - // Construct state image and overlay image indices. They are zero based, btw. - // and -1 means there is no image. - ImageIndex := -1; - DoGetImageIndex(Node, ikState, -1, Ghosted, ImageIndex); - Item.state := Item.state or Byte(IndexToStateImageMask(ImageIndex + 1)); - ImageIndex := -1; - DoGetImageIndex(Node, ikOverlay, -1, Ghosted, ImageIndex); - Item.state := Item.state or Byte(IndexToOverlayMask(ImageIndex + 1)); - end; - // Node caption requested? - if (Item.mask and TVIF_TEXT) <> 0 then - begin - GetTextInfo(Node, -1, Font, R, Text); - - StrLCopy(Item.pszText, PWideChar(Text), Item.cchTextMax - 1); - Item.pszText[Length(Text)] := #0; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.TVMGetItemRect(var Message: TMessage); - -// Screen read support function. This method returns a node's display rectangle. - -var - TextOnly: Boolean; - Node: PVirtualNode; - -begin - // The lparam member is used two-way. On enter it contains a pointer to the item (node). - // On exit it is to be considered as pointer to a rectangle structure. - Node := Pointer(Pointer(Message.LParam)^); - Message.Result := Ord(IsVisible[Node]); - if Message.Result <> 0 then - begin - TextOnly := Message.WParam <> 0; - PRect(Message.LParam)^ := GetDisplayRect(Node, NoColumn, TextOnly); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.TVMGetNextItem(var Message: TMessage); - -// Screen read support function. This method returns a node depending on the requested case. - -var - Node: PVirtualNode; - -begin - // Start with a nil result. - Message.Result := 0; - Node := Pointer(Message.LParam); - case Message.WParam of - TVGN_CARET: - Message.Result := LRESULT(FFocusedNode); - TVGN_CHILD: - if Assigned(Node) then - Message.Result := LRESULT(GetFirstChild(Node)); - TVGN_DROPHILITE: - Message.Result := LRESULT(FDropTargetNode); - TVGN_FIRSTVISIBLE: - Message.Result := LRESULT(GetFirstVisible(nil, True)); - TVGN_LASTVISIBLE: - Message.Result := LRESULT(GetLastVisible(nil, True)); - TVGN_NEXT: - if Assigned(Node) then - Message.Result := LRESULT(GetNextSibling(Node)); - TVGN_NEXTVISIBLE: - if Assigned(Node) then - Message.Result := LRESULT(GetNextVisible(Node, True)); - TVGN_PARENT: - if Assigned(Node) and (Node <> FRoot) and (Node.Parent <> FRoot) then - Message.Result := LRESULT(Node.Parent); - TVGN_PREVIOUS: - if Assigned(Node) then - Message.Result := LRESULT(GetPreviousSibling(Node)); - TVGN_PREVIOUSVISIBLE: - if Assigned(Node) then - Message.Result := LRESULT(GetPreviousVisible(Node, True)); - TVGN_ROOT: - Message.Result := LRESULT(GetFirst); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMCancelMode(var Message: TWMCancelMode); - -begin - // Clear any transient state. - StopTimer(ExpandTimer); - StopTimer(EditTimer); - StopTimer(HeaderTimer); - StopTimer(ScrollTimer); - StopTimer(SearchTimer); - StopTimer(ThemeChangedTimer); - FSearchBuffer := ''; - FLastSearchNode := nil; - - DoStateChange([], [tsClearPending, tsEditPending, tsOLEDragPending, tsVCLDragPending, tsDrawSelecting, - tsDrawSelPending, tsIncrementalSearching]); - - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMChar(var Message: TWMChar); - -begin - if tsIncrementalSearchPending in FStates then - begin - HandleIncrementalSearch(Message.CharCode); - DoStateChange([], [tsIncrementalSearchPending]); - end; - - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMContextMenu(var Message: TWMContextMenu); - -// This method is called when a popup menu is about to be displayed. -// We have to cancel some pending states here to avoid interferences. - -begin - DoStateChange([], [tsClearPending, tsEditPending, tsOLEDragPending, tsVCLDragPending]); - - if not (tsPopupMenuShown in FStates) then - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMCopy(var Message: TWMCopy); - -begin - CopyToClipboard; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMCut(var Message: TWMCut); - -begin - CutToClipboard; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMEnable(var Message: TWMEnable); - -begin - inherited; - RedrawWindow(Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMEraseBkgnd(var Message: TWMEraseBkgnd); - -begin - Message.Result := 1; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMGetDlgCode(var Message: TWMGetDlgCode); - -begin - Message.Result := DLGC_WANTCHARS or DLGC_WANTARROWS; - if FWantTabs then - Message.Result := Message.Result or DLGC_WANTTAB; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMGetObject(var Message: TMessage); - -begin - if TVTAccessibilityFactory.GetAccessibilityFactory <> nil then - begin - // Create the IAccessibles for the tree view and tree view items, if necessary. - if FAccessible = nil then - FAccessible := TVTAccessibilityFactory.GetAccessibilityFactory.CreateIAccessible(Self); - if FAccessibleItem = nil then - FAccessibleItem := TVTAccessibilityFactory.GetAccessibilityFactory.CreateIAccessible(Self); - if Cardinal(Message.LParam) = OBJID_CLIENT then - if Assigned(Accessible) then - Message.Result := LresultFromObject(IID_IAccessible, Message.WParam, FAccessible) - else - Message.Result := 0; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMHScroll(var Message: TWMHScroll); - - //--------------- local functions ------------------------------------------- - - function GetRealScrollPosition: Integer; - - var - SI: TScrollInfo; - Code: Integer; - - begin - SI.cbSize := SizeOf(TScrollInfo); - SI.fMask := SIF_TRACKPOS; - Code := SB_HORZ; - GetScrollInfo(Handle, Code, SI); - Result := SI.nTrackPos; - end; - - //--------------- end local functions --------------------------------------- - -var - RTLFactor: Integer; - -begin - if UseRightToLeftAlignment then - RTLFactor := -1 - else - RTLFactor := 1; - - case Message.ScrollCode of - SB_BOTTOM: - SetOffsetX(-Integer(FRangeX)); - SB_ENDSCROLL: - begin - DoStateChange([], [tsThumbTracking]); - // avoiding to adjust the vertical scroll position while tracking makes it much smoother - // but we need to adjust the final position here then - UpdateHorizontalScrollBar(False); - end; - SB_LINELEFT: - SetOffsetX(FOffsetX + RTLFactor * FScrollBarOptions.HorizontalIncrement); - SB_LINERIGHT: - SetOffsetX(FOffsetX - RTLFactor * FScrollBarOptions.HorizontalIncrement); - SB_PAGELEFT: - SetOffsetX(FOffsetX + RTLFactor * (ClientWidth - FHeader.Columns.GetVisibleFixedWidth)); - SB_PAGERIGHT: - SetOffsetX(FOffsetX - RTLFactor * (ClientWidth - FHeader.Columns.GetVisibleFixedWidth)); - SB_THUMBPOSITION, - SB_THUMBTRACK: - begin - DoStateChange([tsThumbTracking]); - if UseRightToLeftAlignment then - SetOffsetX(-Integer(FRangeX) + ClientWidth + GetRealScrollPosition) - else - SetOffsetX(-GetRealScrollPosition); - end; - SB_TOP: - SetOffsetX(0); - end; - - Message.Result := 0; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMKeyDown(var Message: TWMKeyDown); - -// Keyboard event handling for node focus, selection, node specific popup menus and help invokation. -// For a detailed description of every action done here read the help. - -var - Shift: TShiftState; - Node, Temp, - LastFocused: PVirtualNode; - Offset: Integer; - ClearPending, - NeedInvalidate, - DoRangeSelect, - PerformMultiSelect: Boolean; - Context: Integer; - ParentControl: TWinControl; - R: TRect; - NewCheckState: TCheckState; - TempColumn, - NewColumn: TColumnIndex; - ActAsGrid: Boolean; - ForceSelection: Boolean; - NewWidth, - NewHeight: Integer; - RTLFactor: Integer; - - // for tabulator handling - GetStartColumn: function(ConsiderAllowFocus: Boolean = False): TColumnIndex of object; - GetNextColumn: function(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex of object; - GetNextNode: TGetNextNodeProc; - - KeyState: TKeyboardState; - Buffer: array[0..1] of AnsiChar; - - //--------------- local functions ------------------------------------------- - function getPreviousVisibleAutoSpanColumn(acolumn: TColumnIndex; anode: PVirtualNode): TColumnIndex; - var - PrevColumn: Integer; - begin - if (not assigned(anode)) - or (not FHeader.UseColumns) - or (not (toAutoSpanColumns in FOptions.AutoOptions)) - or (acolumn = FHeader.MainColumn) then - begin - //previously existing logic - result := FHeader.Columns.GetPreviousVisibleColumn(acolumn, True); - exit; - end; - //consider auto spanning - with FHeader.Columns do //standard loop for auto span - begin - PrevColumn := acolumn; - repeat - result := FHeader.Columns.GetPreviousVisibleColumn(PrevColumn); - if (result = InvalidColumn) or - (not ColumnIsEmpty(anode, result)) - //Any other BidiMode is not supported as already - //documented by original developer - or (Items[result].BidiMode <> bdLeftToRight) then - Break; - PrevColumn := result; - until False; - end; - end; - - //--------------------------------------------------------------------------- - function getNextVisibleAutoSpanColumn(acolumn: TColumnIndex; anode: PVirtualNode): TColumnIndex; - var - NextColumn: Integer; - begin - if (not assigned(anode)) - or (not FHeader.UseColumns) - or (not (toAutoSpanColumns in FOptions.AutoOptions)) - or (acolumn = FHeader.MainColumn) then - begin - //previously existing logic - result := FHeader.Columns.GetNextVisibleColumn(acolumn, True); - exit; - end; - //consider auto spanning - with FHeader.Columns do //standard loop for auto span - begin - NextColumn := acolumn; - repeat - result := FHeader.Columns.GetNextVisibleColumn(NextColumn); - if (result = InvalidColumn) or - not ColumnIsEmpty(anode, result) - //Any other BidiMode is not supported as already - //documented by original developer - or (Items[result].BidiMode <> bdLeftToRight) then - Break; - NextColumn := result; - until False; - end; - end; - - //--------------------------------------------------------------------------- - function isEmptyAutoSpanColumn(acolumn: TColumnIndex; anode: PVirtualNode): boolean; - var - previousColumn: Integer; - begin - result := false; - if (not assigned(anode)) - or (not FHeader.UseColumns) - or (not (toAutoSpanColumns in FOptions.AutoOptions)) - or (acolumn = FHeader.MainColumn) then - exit; - with FHeader.Columns do - begin - previousColumn := FHeader.Columns.GetPreviousVisibleColumn(acolumn); - if (previousColumn = InvalidColumn) //there is no previous column - //Any other BidiMode is not supported as already - //documented by original developer - or (Items[acolumn].BidiMode <> bdLeftToRight) then - exit; //returning false - result := ColumnIsEmpty(anode, acolumn); - end; - end; - - - //--------------- end local functions --------------------------------------- - -begin - // Make form key preview work and let application modify the key if it wants this. - inherited; - - with Message do - begin - Shift := KeyDataToShiftState(KeyData); - // Ask the application if the default key handling is desired. - if DoKeyAction(CharCode, Shift) then - begin - if (CharCode in [VK_HOME, VK_END, VK_PRIOR, VK_NEXT, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_BACK, VK_TAB]) and (RootNode.FirstChild <> nil) then - begin - PerformMultiSelect := (ssShift in Shift) and (toMultiSelect in FOptions.SelectionOptions) and not IsEditing; - - // Flag to avoid range selection in case of single node advance. - DoRangeSelect := (CharCode in [VK_HOME, VK_END, VK_PRIOR, VK_NEXT]) and PerformMultiSelect and not IsEditing; - - NeedInvalidate := DoRangeSelect or (FSelectionCount > 1); - ActAsGrid := toGridExtensions in FOptions.MiscOptions; - ClearPending := (Shift = []) or (ActAsGrid and not (ssShift in Shift)) or - not (toMultiSelect in FOptions.SelectionOptions) or (CharCode in [VK_TAB, VK_BACK]); - - // Keep old focused node for range selection. Use a default node if none was focused until now. - LastFocused := FFocusedNode; - if (LastFocused = nil) and (Shift <> []) then - LastFocused := GetFirstVisible(nil, True); - - // Set an initial range anchor if there is not yet one. - if FRangeAnchor = nil then - FRangeAnchor := GetFirstSelected; - if FRangeAnchor = nil then - FRangeAnchor := GetFirst; - - if UseRightToLeftAlignment then - RTLFactor := -1 - else - RTLFactor := 1; - - // Determine new focused node. - case CharCode of - VK_HOME, VK_END: - begin - if (CharCode = VK_END) xor UseRightToLeftAlignment then - begin - GetStartColumn := FHeader.Columns.GetLastVisibleColumn; - GetNextColumn := FHeader.Columns.GetPreviousVisibleColumn; - GetNextNode := GetPreviousVisible; - Node := GetLastVisible(nil, True); - end - else - begin - GetStartColumn := FHeader.Columns.GetFirstVisibleColumn; - GetNextColumn := FHeader.Columns.GetNextVisibleColumn; - GetNextNode := GetNextVisible; - Node := GetFirstVisible(nil, True); - end; - - // Advance to next/previous visible column. - if FHeader.UseColumns then - NewColumn := GetStartColumn - else - NewColumn := NoColumn; - // Find a column for the new/current node which can be focused. - // Make the 'DoFocusChanging' for finding a valid column - // identifiable from the 'DoFocusChanging' raised later on by - // "FocusedNode := Node;" - while (NewColumn > NoColumn) and not DoFocusChanging(FFocusedNode, FFocusedNode, FFocusedColumn, NewColumn) do - NewColumn := GetNextColumn(NewColumn); - if NewColumn > InvalidColumn then - begin - if (Shift = [ssCtrl]) and not ActAsGrid then - begin - ScrollIntoView(Node, toCenterScrollIntoView in FOptions.SelectionOptions, - not (toDisableAutoscrollOnFocus in FOptions.AutoOptions)); - if (CharCode = VK_HOME) and not UseRightToLeftAlignment then - SetOffsetX(0) - else - SetOffsetX(-MaxInt); - end - else - begin - if not ActAsGrid or (ssCtrl in Shift) then - FocusedNode := Node; - //fix: In grid mode, if full row select option is ON, - //then also go to the node determined from the earlier logic - if ActAsGrid and (toFullRowSelect in FOptions.SelectionOptions) then - FocusedNode := Node; - if ActAsGrid and not (toFullRowSelect in FOptions.SelectionOptions) then - begin - FocusedColumn := NewColumn; - // fix: If auto span is ON the last column may be a merged column. So take - // care of selecting the whole merged column on END key. - if (CharCode = VK_END) and isEmptyAutoSpanColumn(NewColumn, FFocusedNode) then - FocusedColumn := getPreviousVisibleAutoSpanColumn(NewColumn, FFocusedNode); - end; - end; - end; - end; - VK_PRIOR: - if Shift = [ssCtrl, ssShift] then - SetOffsetX(FOffsetX + ClientWidth) - else - if [ssShift, ssAlt] = Shift then - begin - if FFocusedColumn <= NoColumn then - NewColumn := FHeader.Columns.GetFirstVisibleColumn - else - begin - Offset := FHeader.Columns.GetVisibleFixedWidth; - NewColumn := FFocusedColumn; - while True do - begin - TempColumn := FHeader.Columns.GetPreviousVisibleColumn(NewColumn); - NewWidth := FHeader.Columns[NewColumn].Width; - if (TempColumn <= NoColumn) or - (Offset + NewWidth >= ClientWidth) or - (coFixed in FHeader.Columns[TempColumn].Options) then - Break; - NewColumn := TempColumn; - Inc(Offset, NewWidth); - end; - end; - SetFocusedColumn(NewColumn); - end - else - if ssCtrl in Shift then - SetOffsetY(FOffsetY + ClientHeight) - else - begin - Offset := 0; - // If there's no focused node then just take the very first visible one. - if FFocusedNode = nil then - Node := GetFirstVisible(nil, True) - else - begin - // Go up as many nodes as comprise together a size of ClientHeight. - Node := FFocusedNode; - while True do - begin - Temp := GetPreviousVisible(Node, True); - NewHeight := NodeHeight[Node]; - if (Temp = nil) or (Offset + NewHeight >= ClientHeight) then - Break; - Node := Temp; - Inc(Offset, NodeHeight[Node]); - end; - end; - FocusedNode := Node; - end; - VK_NEXT: - if Shift = [ssCtrl, ssShift] then - SetOffsetX(FOffsetX - ClientWidth) - else - if [ssShift, ssAlt] = Shift then - begin - if FFocusedColumn <= NoColumn then - NewColumn := FHeader.Columns.GetFirstVisibleColumn - else - begin - Offset := FHeader.Columns.GetVisibleFixedWidth; - NewColumn := FFocusedColumn; - while True do - begin - TempColumn := FHeader.Columns.GetNextVisibleColumn(NewColumn); - NewWidth := FHeader.Columns[NewColumn].Width; - if (TempColumn <= NoColumn) or - (Offset + NewWidth >= ClientWidth) or - (coFixed in FHeader.Columns[TempColumn].Options) then - Break; - NewColumn := TempColumn; - Inc(Offset, NewWidth); - end; - end; - SetFocusedColumn(NewColumn); - end - else - if ssCtrl in Shift then - SetOffsetY(FOffsetY - ClientHeight) - else - begin - Offset := 0; - // If there's no focused node then just take the very last one. - if FFocusedNode = nil then - Node := GetLastVisible(nil, True) - else - begin - // Go up as many nodes as comprise together a size of ClientHeight. - Node := FFocusedNode; - while True do - begin - Temp := GetNextVisible(Node, True); - NewHeight := NodeHeight[Node]; - if (Temp = nil) or (Offset + NewHeight >= ClientHeight) then - Break; - Node := Temp; - Inc(Offset, NewHeight); - end; - end; - FocusedNode := Node; - end; - VK_UP: - begin - // scrolling without selection change - if ssCtrl in Shift then - SetOffsetY(FOffsetY + Integer(FDefaultNodeHeight)) - else - begin - if FFocusedNode = nil then - Node := GetLastVisible(nil, True) - else - Node := GetPreviousVisible(FFocusedNode, True); - - if Assigned(Node) then - begin - if not EndEditNode then - exit; - if (not PerformMultiSelect or (CompareNodePositions(LastFocused, Node) < -1)) and Assigned(FFocusedNode) then - ClearSelection(False); // Clear selection only if more than one node was skipped. See issue #926 - if FFocusedColumn <= NoColumn then - FFocusedColumn := FHeader.MainColumn; - FocusedNode := Node; - end - else - if Assigned(FFocusedNode) then - InvalidateNode(FFocusedNode); - end; - end; - VK_DOWN: - begin - // scrolling without selection change - if ssCtrl in Shift then - SetOffsetY(FOffsetY - Integer(FDefaultNodeHeight)) - else - begin - if FFocusedNode = nil then - Node := GetFirstVisible(nil, True) - else - Node := GetNextVisible(FFocusedNode, True); - - if Assigned(Node) then - begin - if not EndEditNode then - exit; - if (not PerformMultiSelect or (CompareNodePositions(LastFocused, Node) > 1)) and Assigned(FFocusedNode) then - ClearSelection(False); // Clear selection only if more than one node was skipped. See issue #926 - if FFocusedColumn <= NoColumn then - FFocusedColumn := FHeader.MainColumn; - FocusedNode := Node; - end - else - if Assigned(FFocusedNode) then - InvalidateNode(FFocusedNode); - end; - end; - VK_LEFT: - begin - // special handling - if ssCtrl in Shift then - SetOffsetX(FOffsetX + RTLFactor * FHeader.Columns.GetScrollWidth) - else - begin - // other special cases - Context := NoColumn; - if (toExtendedFocus in FOptions.SelectionOptions) and (toGridExtensions in FOptions.MiscOptions) then - begin - Context := getPreviousVisibleAutoSpanColumn(FFocusedColumn, FFocusedNode); - if Context > NoColumn then - FocusedColumn := Context; - end - else - if Assigned(FFocusedNode) and (vsExpanded in FFocusedNode.States) and - (Shift = []) and (vsHasChildren in FFocusedNode.States) then - ToggleNode(FFocusedNode) - else - begin - if FFocusedNode = nil then - FocusedNode := GetFirstVisible(nil, True) - else - begin - if FFocusedNode.Parent <> FRoot then - Node := FFocusedNode.Parent - else - Node := nil; - if Assigned(Node) then - begin - if PerformMultiSelect then - begin - // and a third special case - if FFocusedNode.Index > 0 then - DoRangeSelect := True - else - if CompareNodePositions(Node, FRangeAnchor) > 0 then - RemoveFromSelection(FFocusedNode); - end; - FocusedNode := Node; - end - else begin - // If already a root node is selected, then scroll to the left as there is nothing else we could do. #691 - SetOffsetX(FOffsetX + RTLFactor * FHeader.Columns.GetScrollWidth); - end;//else - end; - end; - end; - end; - VK_RIGHT: - begin - // special handling - if ssCtrl in Shift then - SetOffsetX(FOffsetX - RTLFactor * FHeader.Columns.GetScrollWidth) - else - begin - // other special cases - Context := NoColumn; - if (toExtendedFocus in FOptions.SelectionOptions) and (toGridExtensions in FOptions.MiscOptions) then - begin - Context := getNextVisibleAutoSpanColumn(FFocusedColumn, FFocusedNode); - if Context > NoColumn then - FocusedColumn := Context; - end - else - if Assigned(FFocusedNode) and not (vsExpanded in FFocusedNode.States) and - (Shift = []) and (vsHasChildren in FFocusedNode.States) then - ToggleNode(FFocusedNode) - else - begin - if FFocusedNode = nil then - FocusedNode := GetFirstVisible(nil, True) - else - begin - Node := GetFirstVisibleChild(FFocusedNode); - if Assigned(Node) then - begin - if PerformMultiSelect and (CompareNodePositions(Node, FRangeAnchor) < 0) then - RemoveFromSelection(FFocusedNode); - FocusedNode := Node; - end - else begin - // If already a leaf node is selected, then scroll to the right as there is nothing else we could do. #691 - SetOffsetX(FOffsetX - RTLFactor * FHeader.Columns.GetScrollWidth); - end;//else - end; - end; - end; - end; - VK_BACK: - if tsIncrementalSearching in FStates then - DoStateChange([tsIncrementalSearchPending]) - else - if Assigned(FFocusedNode) and (FFocusedNode.Parent <> FRoot) then - FocusedNode := FocusedNode.Parent; - VK_TAB: - if (toExtendedFocus in FOptions.SelectionOptions) and FHeader.UseColumns then - begin - // In order to avoid duplicating source code just to change the direction - // we use function variables. - if ssShift in Shift then - begin - GetStartColumn := FHeader.Columns.GetLastVisibleColumn; - GetNextColumn := FHeader.Columns.GetPreviousVisibleColumn; - GetNextNode := GetPreviousVisible; - end - else - begin - GetStartColumn := FHeader.Columns.GetFirstVisibleColumn; - GetNextColumn := FHeader.Columns.GetNextVisibleColumn; - GetNextNode := GetNextVisible; - end; - - // Advance to next/previous visible column/node. - Node := FFocusedNode; - NewColumn := GetNextColumn(FFocusedColumn, True); - repeat - // Find a column for the current node which can be focused. - while (NewColumn > NoColumn) and not DoFocusChanging(FFocusedNode, Node, FFocusedColumn, NewColumn) - //Fix: for Tab Key to properly skip the empty auto span column - or isEmptyAutoSpanColumn(NewColumn, Node) do - NewColumn := GetNextColumn(NewColumn, True); - - if NewColumn > NoColumn then - begin - // Set new node and column in one go. - SetFocusedNodeAndColumn(Node, NewColumn); - Break; - end; - - // No next column was accepted for the current node. So advance to next node and try again. - Node := GetNextNode(Node); - NewColumn := GetStartColumn; - - // fix: From last column, the Tab key should always go to next row irrespective of auto span - // Similarly the Shift-Tab key should go to previos row from first column - if (Node <> nil) and (NewColumn > NoColumn) then - SetFocusedNodeAndColumn(Node, NewColumn); - - until Node = nil; - end; - end; - - // Clear old selection if required but take care to select the new focused node if it was not selected before. - ForceSelection := False; - if ClearPending and ((LastFocused <> FFocusedNode) or (FSelectionCount <> 1)) then - begin - ClearSelection(not Assigned(FFocusedNode)); - ForceSelection := True; - end; - - // Determine new selection anchor. - if Shift = [] then - begin - FRangeAnchor := FFocusedNode; - FLastSelectionLevel := GetNodeLevelForSelectConstraint(FFocusedNode); - end; - - if Assigned(FFocusedNode) then - begin - // Finally change the selection for a specific range of nodes. - if DoRangeSelect then - ToggleSelection(LastFocused, FFocusedNode) - // Make sure the new focused node is also selected. - else if (LastFocused <> FFocusedNode) then begin - if ForceSelection then - AddToSelection(FFocusedNode, False) - else - ToggleSelection(LastFocused, FFocusedNode); // See issue #926 - end; - end; - - // If a repaint is needed then paint the entire tree because of the ClearSelection call, - if NeedInvalidate then - Invalidate; - end - else - begin - // Second chance for keys not directly concerned with selection changes. - - // For +, -, /, * keys on the main keyboard (not numpad) there is no virtual key code defined. - // We have to do special processing to get them working too. - GetKeyboardState(KeyState); - // Avoid conversion to control characters. We have captured the control key state already in Shift. - KeyState[VK_CONTROL] := 0; - if ToASCII(Message.CharCode, (Message.KeyData shr 16) and 7, KeyState, PChar(@Buffer), 0) > 0 then - begin - case Buffer[0] of - '*': - CharCode := VK_MULTIPLY; - '+': - CharCode := VK_ADD; - '/': - CharCode := VK_DIVIDE; - '-': - CharCode := VK_SUBTRACT; - end; - end; - - // According to https://web.archive.org/web/20041129085958/http://www.it-faq.pl/mskb/99/337.HTM - // there is a problem with ToASCII when used in conjunction with dead chars. - // The article recommends to call ToASCII twice to restore a deleted flag in the key message - // structure under certain circumstances. It turned out it is best to always call ToASCII twice. - ToASCII(Message.CharCode, (Message.KeyData shr 16) and 7, KeyState, PChar(@Buffer), 0); - - case CharCode of - VK_F2: - if (Shift = []) and Assigned(FFocusedNode) and CanEdit(FFocusedNode, FFocusedColumn) then - begin - FEditColumn := FFocusedColumn; - DoEdit; - end; - VK_ADD: - if not (tsIncrementalSearching in FStates) then - begin - if ssCtrl in Shift then begin// When changing this code review issue #781 - if ((toReverseFullExpandHotKey in TreeOptions.MiscOptions) and (Shift = [ssCtrl])) xor (Shift = [ssCtrl, ssShift]) then - FullExpand - else if Shift = [ssCtrl] then - FHeader.AutoFitColumns - end - else if Shift = [] then begin - if Assigned(FFocusedNode) and not (vsExpanded in FFocusedNode.States) then - ToggleNode(FFocusedNode); - end// if Shift = [] - else - DoStateChange([tsIncrementalSearchPending]); - end;//if not (tsIncrementalSearching in FStates) - VK_SUBTRACT: - if not (tsIncrementalSearching in FStates) then - begin - if ssCtrl in Shift then - if (toReverseFullExpandHotKey in TreeOptions.MiscOptions) xor (ssShift in Shift) then - FullCollapse - else - FHeader.RestoreColumns - else - if Assigned(FFocusedNode) and (vsExpanded in FFocusedNode.States) then - ToggleNode(FFocusedNode); - end - else - DoStateChange([tsIncrementalSearchPending]); - VK_MULTIPLY: - if not (tsIncrementalSearching in FStates) then - begin - if Assigned(FFocusedNode) then - FullExpand(FFocusedNode); - end - else - DoStateChange([tsIncrementalSearchPending]); - VK_DIVIDE: - if not (tsIncrementalSearching in FStates) then - begin - if Assigned(FFocusedNode) then - FullCollapse(FFocusedNode); - end - else - DoStateChange([tsIncrementalSearchPending]); - VK_ESCAPE: // cancel actions currently in progress - begin - if IsMouseSelecting then - begin - DoStateChange([], [tsDrawSelecting, tsDrawSelPending]); - Invalidate; - end - else - if IsEditing then - CancelEditNode; - end; - VK_SPACE: - if (toCheckSupport in FOptions.MiscOptions) and Assigned(FFocusedNode) and - (FFocusedNode.CheckType <> ctNone) then - begin - NewCheckState := DetermineNextCheckState(FFocusedNode.CheckType, GetCheckState(FFocusedNode)); - if DoChecking(FFocusedNode, NewCheckState) then - begin - if SelectedCount > 1 then - SetCheckStateForAll(NewCheckState, True) - else - DoCheckClick(FFocusedNode, NewCheckState); - end; - end - else - DoStateChange([tsIncrementalSearchPending]); - VK_F1: - if Assigned(FOnGetHelpContext) then - begin - Context := 0; - if Assigned(FFocusedNode) then - begin - Node := FFocusedNode; - // Traverse the tree structure up to the root. - repeat - FOnGetHelpContext(Self, Node, IfThen(FFocusedColumn > NoColumn, FFocusedColumn, 0), Context); - Node := Node.Parent; - until (Node = FRoot) or (Context <> 0); - end; - - // If no help context could be found try the tree's one or its parent's contexts. - ParentControl := Self; - while Assigned(ParentControl) and (Context = 0) do - begin - Context := ParentControl.HelpContext; - ParentControl := ParentControl.Parent; - end; - if Context <> 0 then - Application.HelpContext(Context); - end; - VK_APPS: - if Assigned(FFocusedNode) then - begin - R := GetDisplayRect(FFocusedNode, FFocusedColumn, True); - Offset := DoGetNodeWidth(FFocusedNode, FFocusedColumn); - if FFocusedColumn >= 0 then - begin - if Offset > FHeader.Columns[FFocusedColumn].Width then - Offset := FHeader.Columns[FFocusedColumn].Width; - end - else - begin - if Offset > ClientWidth then - Offset := ClientWidth; - end; - DoPopupMenu(FFocusedNode, FFocusedColumn, Point(R.Left + Offset div 2, (R.Top + R.Bottom) div 2)); - end - else - DoPopupMenu(nil, FFocusedColumn, Point(-1, -1)); - Ord('a'), Ord('A'): - if ssCtrl in Shift then - SelectAll(True) - else - DoStateChange([tsIncrementalSearchPending]); - else - begin - // Use the key for incremental search. - // Since we are dealing with Unicode all the time there should be a more sophisticated way - // of checking for valid characters for incremental search. - // This is available but would require to include a significant amount of Unicode character - // properties, so we stick with the simple space check. - if ((Shift * [ssCtrl, ssAlt] = []) or ((Shift * [ssCtrl, ssAlt] = [ssCtrl, ssAlt]))) and (CharCode >= 32) then - DoStateChange([tsIncrementalSearchPending]); - end; - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMKeyUp(var Message: TWMKeyUp); - -begin - inherited; - - case Message.CharCode of - VK_TAB: - EnsureNodeFocused(); // Always select a node if the control gets the focus via TAB key, #237 - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMKillFocus(var Msg: TWMKillFocus); - -var - Form: TCustomForm; - Control: TWinControl; - Pos: TSmallPoint; - Unknown: IUnknown; - -begin - inherited; - - // Remove hint if shown currently. - if tsHint in Self.FStates then - Application.CancelHint; - - // Stop wheel panning if active. - StopWheelPanning; - - // Don't let any timer continue if the tree is no longer the active control (except change timers). - StopTimer(ExpandTimer); - StopTimer(EditTimer); - StopTimer(HeaderTimer); - StopTimer(ScrollTimer); - StopTimer(SearchTimer); - FSearchBuffer := ''; - FLastSearchNode := nil; - - DoStateChange([], [tsScrollPending, tsScrolling, tsEditPending, tsLeftButtonDown, tsRightButtonDown, - tsMiddleButtonDown, tsOLEDragPending, tsVCLDragPending, tsIncrementalSearching, tsNodeHeightTrackPending, - tsNodeHeightTracking]); - - if (FSelectionCount > 0) or not (toGhostedIfUnfocused in FOptions.PaintOptions) then - Invalidate - else - if Assigned(FFocusedNode) then - InvalidateNode(FFocusedNode); - - // Workaround for wrapped non-VCL controls (like TWebBrowser), which do not use VCL mechanisms and - // leave the ActiveControl property in the wrong state, which causes trouble when the control is refocused. - Form := GetParentForm(Self); - if Assigned(Form) and (Form.ActiveControl = Self) then - begin - Cardinal(Pos) := GetMessagePos; - Control := FindVCLWindow(SmallPointToPoint(Pos)); - // Every control derived from TOleControl has potentially the focus problem. In order to avoid including - // the OleCtrls unit (which will, among others, include Variants), which would allow to test for the TOleControl - // class, the IOleClientSite interface is used for the test, which is supported by TOleControl and a good indicator. - if Assigned(Control) and Control.GetInterface(IOleClientSite, Unknown) then - Form.ActiveControl := nil; - - // For other classes the active control should not be modified. Otherwise you need two clicks to select it. - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMLButtonDblClk(var Message: TWMLButtonDblClk); - -var - HitInfo: THitInfo; - -begin - DoStateChange([tsLeftDblClick]); - try - // get information about the hit, before calling inherited, is this may change the scroll postion and so the node under the mouse would chnage and would no longer be the one the user actually clicked - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - HandleMouseDblClick(Message, HitInfo); - // Call inherited after doing our standard handling, as the event handler may close the form or re-fill the control, so our clicked node would be no longer valid. - // Our standard handling does not do that. - inherited; - // #909 - // if we show a modal form in the HandleMouseDblClick(), the mouse capture wont be released - if csCaptureMouse in ControlStyle then MouseCapture := False; - finally - DoStateChange([], [tsLeftDblClick]); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMLButtonDown(var Message: TWMLButtonDown); - -var - HitInfo: THitInfo; - -begin - DoStateChange([tsLeftButtonDown]); - inherited; - - // get information about the hit - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - HandleMouseDown(Message, HitInfo); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMLButtonUp(var Message: TWMLButtonUp); - -var - HitInfo: THitInfo; - -begin - DoStateChange([], [tsLeftButtonDown, tsNodeHeightTracking, tsNodeHeightTrackPending]); - - // get information about the hit - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - HandleMouseUp(Message, HitInfo); - - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMMButtonDblClk(var Message: TWMMButtonDblClk); - -var - HitInfo: THitInfo; - -begin - DoStateChange([tsMiddleDblClick]); - inherited; - - // get information about the hit - if toMiddleClickSelect in FOptions.SelectionOptions then - begin - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - HandleMouseDblClick(Message, HitInfo); - end; - DoStateChange([], [tsMiddleDblClick]); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMMButtonDown(var Message: TWMMButtonDown); - -var - HitInfo: THitInfo; - -begin - DoStateChange([tsMiddleButtonDown]); - - if FHeader.States = [] then - begin - inherited; - - // Start wheel panning or scrolling if not already active, allowed and scrolling is useful at all. - if (toWheelPanning in FOptions.MiscOptions) and ([tsWheelScrolling, tsWheelPanning] * FStates = []) and - ((Integer(FRangeX) > ClientWidth) or (Integer(FRangeY) > ClientHeight)) then - begin - FLastClickPos := SmallPointToPoint(Message.Pos); - StartWheelPanning(FLastClickPos); - end - else - begin - StopWheelPanning; - - // Get information about the hit. - if toMiddleClickSelect in FOptions.SelectionOptions then - begin - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - HandleMouseDown(Message, HitInfo); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMMButtonUp(var Message: TWMMButtonUp); - -var - HitInfo: THitInfo; - -begin - DoStateChange([], [tsMiddleButtonDown]); - - // If wheel panning/scrolling is active and the mouse has not yet been moved then the user starts wheel auto scrolling. - // Indicate this by removing the panning flag. Otherwise (the mouse has moved meanwhile) stop panning. - if [tsWheelPanning, tsWheelScrolling] * FStates <> [] then - begin - if tsWheelScrolling in FStates then - DoStateChange([], [tsWheelPanning]) - else - StopWheelPanning; - end - else - if FHeader.States = [] then - begin - inherited; - - // get information about the hit - if toMiddleClickSelect in FOptions.SelectionOptions then - begin - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - HandleMouseUp(Message, HitInfo); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMNCCalcSize(var Message: TWMNCCalcSize); - -begin - inherited; - - with FHeader do - if hoVisible in FHeader.Options then - with Message.CalcSize_Params^ do - Inc(rgrc[0].Top, FHeight); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMNCDestroy(var Message: TWMNCDestroy); - -// Used to release a reference of the drag manager. This is the only reliable way we get notified about -// window destruction, because of the automatic release of a window if its parent window is freed. - -begin - InterruptValidation; - - StopTimer(ChangeTimer); - StopTimer(StructureChangeTimer); - - if not (csDesigning in ComponentState) and (toAcceptOLEDrop in FOptions.MiscOptions) and HandleAllocated then - RevokeDragDrop(Handle); - - // Clean up other stuff. - DeleteObject(FDottedBrush); - FDottedBrush := 0; - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMNCHitTest(var Message: TWMNCHitTest); - -begin - inherited; - if (hoVisible in FHeader.Options) and - FHeader.InHeader(ScreenToClient(SmallPointToPoint(Message.Pos))) then - Message.Result := HTBORDER; -end; - -//---------------------------------------------------------------------------------------------------------------------- - - -procedure TBaseVirtualTree.WMNCPaint(var Message: TWMNCPaint); - -var - DC: HDC; - R: TRect; - Flags: DWORD; - ExStyle: Integer; - TempRgn: HRGN; - BorderWidth, - BorderHeight: Integer; - -begin - if tsUseThemes in FStates then - begin - // If theming is enabled and the client edge border is set for the window then prevent the default window proc - // from painting the old border to avoid flickering. - ExStyle := GetWindowLong(Handle, GWL_EXSTYLE); - if (ExStyle and WS_EX_CLIENTEDGE) <> 0 then - begin - GetWindowRect(Handle, R); - // Determine width of the client edge. - BorderWidth := GetSystemMetrics(SM_CXEDGE); - BorderHeight := GetSystemMetrics(SM_CYEDGE); - InflateRect(R, -BorderWidth, -BorderHeight); - TempRgn := CreateRectRgnIndirect(R); - // Exclude the border from the message region if there is one. Otherwise just use the inflated - // window area region. - if Message.Rgn <> 1 then - CombineRgn(TempRgn, Message.Rgn, TempRgn, RGN_AND); - DefWindowProc(Handle, Message.Msg, WPARAM(TempRgn), 0); - DeleteObject(TempRgn); - end - else - DefaultHandler(Message); - end - else - DefaultHandler(Message); - - Flags := DCX_CACHE or DCX_CLIPSIBLINGS or DCX_WINDOW or DCX_VALIDATE; - - if (Message.Rgn = 1) then - DC := GetDCEx(Handle, 0, Flags) - else - DC := GetDCEx(Handle, Message.Rgn, Flags or DCX_INTERSECTRGN); - - if DC <> 0 then - try - OriginalWMNCPaint(DC); - finally - ReleaseDC(Handle, DC); - end; - if (((tsUseThemes in FStates) and not VclStyleEnabled) or (VclStyleEnabled and (seBorder in StyleElements))) then - StyleServices.PaintBorder(Self, False) - else - if (VclStyleEnabled and not (seBorder in StyleElements)) then - TStyleManager.SystemStyle.PaintBorder(Self, False) -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMPaint(var Message: TWMPaint); -var - DC: HDC; -begin - if tsVCLDragging in FStates then - ImageList_DragShowNolock(False); - if csPaintCopy in ControlState then - FUpdateRect := ClientRect - else - GetUpdateRect(Handle, FUpdateRect, True); - - inherited; - - if tsVCLDragging in FStates then - ImageList_DragShowNolock(True); - - if hoVisible in FHeader.Options then - begin - DC := GetDCEx(Handle, 0, DCX_CACHE or DCX_CLIPSIBLINGS or DCX_WINDOW or DCX_VALIDATE); - if DC <> 0 then - try - FHeader.Columns.PaintHeader(DC, FHeaderRect, -FEffectiveOffsetX); - finally - ReleaseDC(Handle, DC); - end; - end;//if header visible -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMPaste(var Message: TWMPaste); - -begin - PasteFromClipboard; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMPrint(var Message: TWMPrint); - -// This message is sent to request that the tree draws itself to a given device context. This includes not only -// the client area but also the non-client area (header!). - -begin - // Draw only if the window is visible or visibility is not required. - if ((Message.Flags and PRF_CHECKVISIBLE) = 0) or IsWindowVisible(Handle) then - Header.Columns.PaintHeader(Message.DC, FHeaderRect, -FEffectiveOffsetX); - - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMPrintClient(var Message: TWMPrintClient); - -var - Window: TRect; - Target: TPoint; - Canvas: TCanvas; - -begin - // Draw only if the window is visible or visibility is not required. - if ((Message.Flags and PRF_CHECKVISIBLE) = 0) or IsWindowVisible(Handle) then - begin - // Determine area of the entire tree to be displayed in the control. - Window := ClientRect; - Target := Window.TopLeft; - - // The Window rectangle is given in client coordinates. We have to convert it into - // a sliding window of the tree image. - OffsetRect(Window, FEffectiveOffsetX, -FOffsetY); - - Canvas := TCanvas.Create; - try - Canvas.Handle := Message.DC; - PaintTree(Canvas, Window, Target, [poBackground, poDrawFocusRect, poDrawDropMark, poDrawSelection, poGridLines]); - finally - Canvas.Handle := 0; - Canvas.Free; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMRButtonDblClk(var Message: TWMRButtonDblClk); - -var - HitInfo: THitInfo; - -begin - DoStateChange([tsRightDblClick]); - inherited; - - // get information about the hit - if toMiddleClickSelect in FOptions.SelectionOptions then - begin - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - HandleMouseDblClick(Message, HitInfo); - end; - DoStateChange([], [tsRightDblClick]); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMRButtonDown(var Message: TWMRButtonDown); - -var - HitInfo: THitInfo; - RemoveSynchMode: Boolean; // Needed to restore tsSynchMode correctly - -begin - DoStateChange([tsRightButtonDown]); - - if FHeader.States = [] then - begin - inherited; - - // get information about the hit - if toRightClickSelect in FOptions.SelectionOptions then - begin - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - // Go temporarily into sync mode to avoid a delayed change event for the node when selecting. #679 - RemoveSynchMode := not (tsSynchMode in FStates); - Include(FStates, tsSynchMode); - HandleMouseDown(Message, HitInfo); - if RemoveSynchMode then - Exclude(FStates, tsSynchMode); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMRButtonUp(var Message: TWMRButtonUp); - -// handle right click selection and node specific popup menu - -var - HitInfo: THitInfo; - -begin - DoStateChange([], [tsPopupMenuShown, tsRightButtonDown]); - - if FHeader.States = [] then - begin - Application.CancelHint; - - if IsMouseSelecting and Assigned(PopupMenu) then - begin - // Reset selection state already here, before the inherited handler opens the default menu. - DoStateChange([], [tsDrawSelecting, tsDrawSelPending]); - Invalidate; - end; - - inherited; - - // get information about the hit - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - - if toRightClickSelect in FOptions.SelectionOptions then - HandleMouseUp(Message, HitInfo); - - if not Assigned(PopupMenu) then - DoPopupMenu(HitInfo.HitNode, HitInfo.HitColumn, Point(Message.XPos, Message.YPos)); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMSetCursor(var Message: TWMSetCursor); - -// Sets the hot node mouse cursor for the tree. Cursor changes for the header are handled in Header.HandleMessage. - -var - NewCursor: TCursor; - HitInfo: THitInfo; - P: TPoint; - Node: PVirtualNode; - -begin - with Message do - begin - // Feature: design-time header #415 - // Allow header to handle cursor and return control's default if it did nothing - if (CursorWnd = Handle) and - ([tsWheelPanning, tsWheelScrolling] * FStates = []) then - begin - if not FHeader.HandleMessage(TMessage(Message)) then - begin - // Apply own cursors only if there is no global cursor set. - if Screen.Cursor = crDefault then - begin - // node resizing and hot tracking - for run-time only - if not (csDesigning in ComponentState) then - begin - NewCursor := crDefault; - if (toNodeHeightResize in FOptions.MiscOptions) then - begin - GetCursorPos(P); - P := ScreenToClient(P); - GetHitTestInfoAt(P.X, P.Y, True, HitInfo); - if (hiOnItem in HitInfo.HitPositions) and - ([hiUpperSplitter, hiLowerSplitter] * HitInfo.HitPositions <> []) then - begin - if hiUpperSplitter in HitInfo.HitPositions then - Node := GetPreviousVisible(HitInfo.HitNode, True) - else - Node := HitInfo.HitNode; - - if CanSplitterResizeNode(P, Node, HitInfo.HitColumn) then - NewCursor := crVertSplit; - end; - end; - - if (NewCursor = crDefault) then - if (toHotTrack in FOptions.PaintOptions) and Assigned(FCurrentHotNode) and (FHotCursor <> crDefault) then - NewCursor := FHotCursor - else - NewCursor := Cursor; - - DoGetCursor(NewCursor); - end - else - NewCursor := Cursor; - Winapi.Windows.SetCursor(Screen.Cursors[NewCursor]); - Message.Result := 1; - end - else - inherited; - end; - end - else - inherited; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMSetFocus(var Msg: TWMSetFocus); - -begin - inherited; - if (FSelectionCount > 0) or not (toGhostedIfUnfocused in FOptions.PaintOptions) then - Invalidate; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMSize(var Message: TWMSize); - -begin - inherited; - - // Need to update scroll bars here. This will cause a recursion because of the change of the client area - // when changing a scrollbar. Usually this is no problem since with the second level recursion no change of the - // window size happens (the same values for the scrollbars are set, which shouldn't cause a window size change). - // Appearently, this applies not to all systems, however. - if HandleAllocated and ([tsSizing, tsWindowCreating] * FStates = []) and (ClientHeight > 0) then - try - DoStateChange([tsSizing]); - // This call will invalidate the entire non-client area which needs recalculation on resize. - FHeader.RescaleHeader; - FHeader.UpdateSpringColumns; - UpdateScrollBars(True); - - if (tsEditing in FStates) and not FHeader.UseColumns then - UpdateEditBounds; - finally - DoStateChange([], [tsSizing]); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMThemeChanged(var Message: TMessage); - -begin - inherited; - - if StyleServices.Enabled and (toThemeAware in TreeOptions.PaintOptions) then - DoStateChange([tsUseThemes]) - else - DoStateChange([], [tsUseThemes]); - - // Updating the visuals here will not work correctly. Therefore we postpone - // the update by using a timer. - if not FChangingTheme then - SetTimer(Handle, ThemeChangedTimer, ThemeChangedTimerDelay, nil); - FChangingTheme := False; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMTimer(var Message: TWMTimer); - -// centralized timer handling happens here - -begin - with Message do - begin - case TimerID of - ExpandTimer: - DoDragExpand; - EditTimer: - DoEdit; - ScrollTimer: - begin - if tsScrollPending in FStates then - begin - Application.CancelHint; - // Scroll delay has elapsed, set to normal scroll interval now. - SetTimer(Handle, ScrollTimer, FAutoScrollInterval, nil); - DoStateChange([tsScrolling], [tsScrollPending]); - end; - DoTimerScroll; - end; - ChangeTimer: - if tsChangePending in FStates then // see issue #602 - DoChange(FLastChangedNode); - StructureChangeTimer: - DoStructureChange(FLastStructureChangeNode, FLastStructureChangeReason); - SearchTimer: - begin - // When this event triggers then the user did not pressed any key for the specified timeout period. - // Hence incremental searching is stopped. - DoStateChange([], [tsIncrementalSearching]); - StopTimer(SearchTimer); - FSearchBuffer := ''; - FLastSearchNode := nil; - end; - ThemeChangedTimer: - begin - StopTimer(ThemeChangedTimer); - RecreateWnd; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WMVScroll(var Message: TWMVScroll); - - //--------------- local functions ------------------------------------------- - - function GetRealScrollPosition: Integer; - - var - SI: TScrollInfo; - Code: Integer; - - begin - SI.cbSize := SizeOf(TScrollInfo); - SI.fMask := SIF_TRACKPOS; - Code := SB_VERT; - GetScrollInfo(Handle, Code, SI); - Result := SI.nTrackPos; - end; - - //--------------- end local functions --------------------------------------- - -begin - case Message.ScrollCode of - SB_BOTTOM: - SetOffsetY(-Integer(FRoot.TotalHeight)); - SB_ENDSCROLL: - begin - DoStateChange([], [tsThumbTracking]); - // Avoiding to adjust the horizontal scroll position while tracking makes scrolling much smoother - // but we need to adjust the final position here then. - UpdateScrollBars(True); - // Really weird invalidation needed here (and I do it only because it happens so rarely), because - // when showing the horizontal scrollbar while scrolling down using the down arrow button, - // the button will be repainted on mouse up (at the wrong place in the far right lower corner)... - RedrawWindow(Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE or RDW_NOERASE or RDW_NOCHILDREN); - end; - SB_LINEUP: - SetOffsetY(FOffsetY + FScrollBarOptions.VerticalIncrement); - SB_LINEDOWN: - SetOffsetY(FOffsetY - FScrollBarOptions.VerticalIncrement); - SB_PAGEUP: - SetOffsetY(FOffsetY + ClientHeight); - SB_PAGEDOWN: - SetOffsetY(FOffsetY - ClientHeight); - - SB_THUMBPOSITION, - SB_THUMBTRACK: - begin - DoStateChange([tsThumbTracking]); - SetOffsetY(-GetRealScrollPosition); - end; - SB_TOP: - SetOffsetY(0); - end; - Message.Result := 0; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.AddToSelection(Node: PVirtualNode; NotifySynced: Boolean); - -var - Changed: Boolean; - RemoveSyncAfterChange: Boolean; -begin - if not FSelectionLocked then - begin - Assert(Assigned(Node), 'Node must not be nil!'); - Changed := InternalAddToSelection(Node, False); - if Changed then - begin - UpdateNextNodeToSelect(Node); - if (SelectedCount = 1) then - FocusedNode := Node; // if only one node is selected, make sure the focused node changes with the selected node - InvalidateNode(Node); - RemoveSyncAfterChange := NotifySynced and not (tsSynchMode in fStates); - if RemoveSyncAfterChange then - Include(FStates, tsSynchMode); - try - Change(Node); - finally - if RemoveSyncAfterChange then - Exclude(FStates, tsSynchMode); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.AddToSelection(const NewItems: TNodeArray; NewLength: Integer; ForceInsert: Boolean = False); - -// Adds the given items all at once into the current selection array. NewLength is the amount of -// nodes to add (necessary to allow NewItems to be larger than the actual used entries). -// ForceInsert is True if nodes must be inserted without consideration of level select constraint or -// already set selected flags (e.g. when loading from stream). -// Note: In the case ForceInsert is True the caller is responsible for making sure the new nodes aren't already in the -// selection array! - -var - Changed: Boolean; - -begin - Changed := InternalAddToSelection(NewItems, NewLength, ForceInsert); - if Changed then - begin - if NewLength = 1 then - begin - InvalidateNode(NewItems[0]); - Change(NewItems[0]); - end - else - begin - Invalidate; - Change(nil); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.AdjustPaintCellRect(var PaintInfo: TVTPaintInfo; var NextNonEmpty: TColumnIndex); - -// Used in descendants to modify the paint rectangle of the current column while painting a certain node. - -begin - // Since cells are always drawn from left to right the next column index is independent of the - // bidi mode, but not the column borders, which might change depending on the cell's content. - NextNonEmpty := FHeader.Columns.GetNextVisibleColumn(PaintInfo.Column); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.AdjustPanningCursor(X, Y: Integer); - -// Triggered by a mouse move when wheel panning/scrolling is active. -// Loads the proper cursor which indicates into which direction scrolling is done. - -var - Name: string; - NewCursor: HCURSOR; - ScrollHorizontal, - ScrollVertical: Boolean; - -begin - ScrollHorizontal := Integer(FRangeX) > ClientWidth; - ScrollVertical := Integer(FRangeY) > ClientHeight; - - if (Abs(X - FLastClickPos.X) < 8) and (Abs(Y - FLastClickPos.Y) < 8) then - begin - // Mouse is in the neutral zone. - if ScrollHorizontal then - begin - if ScrollVertical then - Name := 'VT_MOVEALL' - else - Name := 'VT_MOVEEW'; - end - else - Name := 'VT_MOVENS'; - end - else - begin - // One of 8 directions applies: north, north-east, east, south-east, south, south-west, west and north-west. - // Check also if scrolling in the particular direction is possible. - if ScrollVertical and ScrollHorizontal then - begin - // All directions allowed. - if X - FLastClickPos.X < -8 then - begin - // Left hand side. - if Y - FLastClickPos.Y < -8 then - Name := 'VT_MOVENW' - else - if Y - FLastClickPos.Y > 8 then - Name := 'VT_MOVESW' - else - Name := 'VT_MOVEW'; - end - else - if X - FLastClickPos.X > 8 then - begin - // Right hand side. - if Y - FLastClickPos.Y < -8 then - Name := 'VT_MOVENE' - else - if Y - FLastClickPos.Y > 8 then - Name := 'VT_MOVESE' - else - Name := 'VT_MOVEE'; - end - else - begin - // Up or down. - if Y < FLastClickPos.Y then - Name := 'VT_MOVEN' - else - Name := 'VT_MOVES'; - end; - end - else - if ScrollHorizontal then - begin - // Only horizontal movement allowed. - if X < FLastClickPos.X then - Name := 'VT_MOVEW' - else - Name := 'VT_MOVEE'; - end - else - begin - // Only vertical movement allowed. - if Y < FLastClickPos.Y then - Name := 'VT_MOVEN' - else - Name := 'VT_MOVES'; - end; - end; - - // Now load the cursor and apply it. - NewCursor := LoadCursor(HInstance, PChar(Name)); - if FPanningCursor <> NewCursor then - begin - DeleteObject(FPanningCursor); - FPanningCursor := NewCursor; - Winapi.Windows.SetCursor(FPanningCursor); - end - else - DeleteObject(NewCursor); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.AdviseChangeEvent(StructureChange: Boolean; Node: PVirtualNode; Reason: TChangeReason); - -// Used to register a delayed change event. If StructureChange is False then we have a selection change event (without -// a specific reason) otherwise it is a structure change. - -begin - if StructureChange then - begin - if tsStructureChangePending in FStates then - StopTimer(StructureChangeTimer) - else - DoStateChange([tsStructureChangePending]); - - FLastStructureChangeNode := Node; - if FLastStructureChangeReason = crIgnore then - FLastStructureChangeReason := Reason - else - if Reason <> crIgnore then - FLastStructureChangeReason := crAccumulated; - end - else - begin - if tsChangePending in FStates then - StopTimer(ChangeTimer) - else - DoStateChange([tsChangePending]); - - FLastChangedNode := Node; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.AllocateInternalDataArea(Size: Cardinal): Cardinal; - -// Simple registration method to be called by each descendant to claim their internal data area. -// Result is the offset from the begin of the node to the internal data area of the calling tree class. - -begin - Assert((FRoot = nil) or (FRoot.ChildCount = 0), 'Internal data allocation must be done before any node is created.'); - Result := TreeNodeSize + FTotalInternalDataSize; - Inc(FTotalInternalDataSize, (Size + (SizeOf(Pointer) - 1)) and not (SizeOf(Pointer) - 1)); - InitRootNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.Animate(Steps, Duration: Cardinal; Callback: TVTAnimationCallback; Data: Pointer); - -// This method does the calculation part of an animation as used for node toggling and hint animations. -// Steps is the maximum amount of animation steps to do and Duration determines the milliseconds the animation -// has to run. Callback is a task specific method which is called in the loop for every step and Data is simply -// something to pass on to the callback. -// The callback is called with the current step, the current step size and the Data parameter. Since the step amount -// as well as the step size are possibly adjusted during the animation, it is impossible to determine if the current -// step is the last step, even if the original step amount is known. To solve this problem the callback will be -// called after the loop has finished with a step size of 0 indicating so to execute any post processing. - -var - StepSize, - RemainingTime, - RemainingSteps, - NextTimeStep, - CurrentStep, - StartTime: Cardinal; - CurrentTime: Int64; - -begin - if not (tsInAnimation in FStates) and (Duration > 0) then - begin - DoStateChange([tsInAnimation]); - try - RemainingTime := Duration; - RemainingSteps := Steps; - - // Determine the initial step size which is either 1 if the needed steps are less than the number of - // steps possible given by the duration or > 1 otherwise. - StepSize := Round(Max(1, RemainingSteps / Duration)); - RemainingSteps := RemainingSteps div StepSize; - CurrentStep := 0; - - while (RemainingSteps > 0) and (RemainingTime > 0) and not Application.Terminated do - begin - StartTime := timeGetTime; - NextTimeStep := StartTime + RemainingTime div RemainingSteps; - if not Callback(CurrentStep, StepSize, Data) then - Break; - - // Keep duration for this step for rest calculation. - CurrentTime := timeGetTime; - // Wait until the calculated time has been reached. - while CurrentTime < NextTimeStep do - CurrentTime := timeGetTime; - - // Subtract the time this step really needed. - if RemainingTime >= CurrentTime - StartTime then - begin - Dec(RemainingTime, CurrentTime - StartTime); - Dec(RemainingSteps); - end - else - begin - RemainingTime := 0; - RemainingSteps := 0; - end; - // If the remaining time per step is less than one time step then we have to decrease the - // step count and increase the step size. - if (RemainingSteps > 0) and ((RemainingTime div RemainingSteps) < 1) then - begin - repeat - Inc(StepSize); - RemainingSteps := RemainingTime div StepSize; - until (RemainingSteps <= 0) or ((RemainingTime div RemainingSteps) >= 1); - end; - CurrentStep := Steps - RemainingSteps; - end; - - if not Application.Terminated then - Callback(0, 0, Data); - finally - DoStateChange([], [tsInAnimation]); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.StartOperation(OperationKind: TVTOperationKind); - -// Called to indicate that a long-running operation has been started. - -begin - Inc(FOperationCount); - if FOperationCount = 1 then - FOperationCanceled := False; - DoStartOperation(OperationKind); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CalculateSelectionRect(X, Y: Integer): Boolean; - -// Recalculates old and new selection rectangle given that X, Y are new mouse coordinates. -// Returns True if there was a change since the last call. - -var - MaxValue: Integer; - -begin - if tsDrawSelecting in FStates then - FLastSelRect := FNewSelRect; - FNewSelRect.BottomRight := Point(X + FEffectiveOffsetX, Y - FOffsetY); - if FNewSelRect.Right < 0 then - FNewSelRect.Right := 0; - if FNewSelRect.Bottom < 0 then - FNewSelRect.Bottom := 0; - MaxValue := ClientWidth; - if FRangeX > Cardinal(MaxValue) then - MaxValue := FRangeX; - if FNewSelRect.Right > MaxValue then - FNewSelRect.Right := MaxValue; - MaxValue := ClientHeight; - if FRangeY > Cardinal(MaxValue) then - MaxValue := FRangeY; - if FNewSelRect.Bottom > MaxValue then - FNewSelRect.Bottom := MaxValue; - - Result := not CompareMem(@FLastSelRect, @FNewSelRect, SizeOf(FNewSelRect)); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CanAutoScroll: Boolean; - -// Determines if auto scrolling is currently allowed. - -var - IsDropTarget: Boolean; - IsDrawSelecting: Boolean; - IsWheelPanning: Boolean; - -begin - // Don't scroll the client area if the header is currently doing tracking or dragging. - // Do auto scroll only if there is a draw selection in progress or the tree is the current drop target or - // wheel panning/scrolling is active. - IsDropTarget := Assigned(FDragManager) and DragManager.IsDropTarget; - IsDrawSelecting := [tsDrawSelPending, tsDrawSelecting] * FStates <> []; - IsWheelPanning := [tsWheelPanning, tsWheelScrolling] * FStates <> []; - Result := ((toAutoScroll in FOptions.AutoOptions) or IsWheelPanning) and - (FHeader.States = []) and (IsDrawSelecting or IsDropTarget or (tsVCLDragging in FStates) or IsWheelPanning); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CanShowDragImage: Boolean; - -// Determines whether a drag image should be shown. - -begin - Result := FDragImageKind <> diNoImage; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex): Boolean; - -begin - Result := (toNodeHeightResize in FOptions.MiscOptions) and Assigned(Node) and (Node <> FRoot) and - (Column > NoColumn) and (coFixed in FHeader.Columns[Column].Options); - DoCanSplitterResizeNode(P, Node, Column, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.Change(Node: PVirtualNode); - -begin - AdviseChangeEvent(False, Node, crIgnore); - - if FUpdateCount = 0 then - begin - if (FChangeDelay > 0) and HandleAllocated and not (tsSynchMode in FStates) then - SetTimer(Handle, ChangeTimer, FChangeDelay, nil) - else - DoChange(Node); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ChangeScale(M, D: Integer{$if CompilerVersion >= 31}; isDpiChange: Boolean{$ifend}); -{$if CompilerVersion < 27} -const - DefaultScalingFlags = [sfLeft, sfTop, sfWidth, sfHeight, sfFont]; // Was introduced with XE6: http://docwiki.embarcadero.com/Libraries/XE6/en/Vcl.Controls.TControl.DefaultScalingFlags -{$ifend} -var - Flags: TScalingFlags; -begin - if (toAutoChangeScale in FOptions.AutoOptions) then - begin - if (M <> D) then - begin - // It is important to evaluate the TScalingFlags before calling inherited, becuase they are differetn afterwards! - if csLoading in ComponentState then - Flags := ScalingFlags - else - Flags := DefaultScalingFlags; // Important for #677 - if (sfHeight in Flags) then begin - FHeader.ChangeScale(M, D, {$if CompilerVersion >= 31}isDpiChange{$ELSE} M <> D{$ifend}); - SetDefaultNodeHeight(MulDiv(FDefaultNodeHeight, M, D)); - Indent := MulDiv(Indent, M, D); - FTextMargin := MulDiv(FTextMargin, M, D); - FMargin := MulDiv(FMargin, M, D); - FImagesMargin := MulDiv(FImagesMargin, M, D); - // Scale utility images, #796 - if FCheckImageKind = ckSystemDefault then begin - FreeAndNil(FCheckImages); - if HandleAllocated then - FCheckImages := CreateSystemImageSet(Self); - end; - UpdateHeaderRect(); - ScaleNodeHeights(M, D); - end;//if sfHeight - end;// if M<>D - end;//if toAutoChangeScale - inherited ChangeScale(M, D{$if CompilerVersion >= 31}, isDpiChange{$ifend}); - if (M <> D) then - PrepareBitmaps(True, False); // See issue #991 - // It is important to do this call after calling inherited, so that the Font has been updated. - AutoScale(M <> D); -end; - - -procedure TBaseVirtualTree.ScaleNodeHeights(M, D: Integer); -var - Run: PVirtualNode; - lNewNodeTotalHeight: Cardinal; -begin - // Scale also node heights - BeginUpdate(); - try - Run := GetFirst(); - while Assigned(Run) do - begin - if vsInitialized in Run.States then - SetNodeHeight(Run, MulDiv(Run.NodeHeight, M, D)) - else // prevent initialization of non-initialzed nodes - begin - Run.NodeHeight := MulDiv(Run.NodeHeight, M, D); - // The next three lines fix issue #1000 - lNewNodeTotalHeight := MulDiv(Run.TotalHeight, M, D); - FRoot.TotalHeight := Cardinal(Int64(FRoot.TotalHeight) + Int64(lNewNodeTotalHeight) - Int64(Run.TotalHeight)); // Avoiding EIntOverflow exception. - end; - Run := GetNextNoInit(Run); - end; // while - finally - EndUpdate(); - end; -end; -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ChangeTreeStatesAsync(EnterStates, LeaveStates: TVirtualTreeStates); -begin - //TODO: If this works reliable, move to TWorkerThread - if not (csDestroying in ComponentState) then - TThread.Synchronize(nil, procedure - begin - // Prevent invalid combination tsUseCache + tsValidationNeeded (#915) - if not ((tsUseCache in EnterStates) and (tsValidationNeeded in FStates + LeaveStates)) then - DoStateChange(EnterStates, LeaveStates); - if (tsValidating in FStates) and (tsValidating in LeaveStates) then - UpdateEditBounds(); - end); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CheckParentCheckState(Node: PVirtualNode; NewCheckState: TCheckState): Boolean; - -// Checks all siblings of node to determine which check state Node's parent must get. - -var - CheckCount, - BoxCount: Cardinal; - PartialCheck: Boolean; - Run: PVirtualNode; - -begin - CheckCount := 0; - BoxCount := 0; - PartialCheck := False; - Run := Node.Parent.FirstChild; - while Assigned(Run) do - begin - if Run = Node then - begin - // The given node cannot be checked because it does not yet have its new check state (as this depends - // on the outcome of this method). Instead NewCheckState is used as this contains the new state the node - // will get if this method returns True. - if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then - begin - Inc(BoxCount); - if NewCheckState.IsChecked then - Inc(CheckCount); - PartialCheck := PartialCheck or (NewCheckState = csMixedNormal); - end; - end - else - if Run.CheckType in [ctCheckBox, ctTriStateCheckBox] then - begin - Inc(BoxCount); - if GetCheckState(Run).IsChecked then - Inc(CheckCount); - PartialCheck := PartialCheck or (GetCheckState(Run) = csMixedNormal); - end; - Run := Run.NextSibling; - end; - - if (CheckCount = 0) and not PartialCheck then - NewCheckState := csUncheckedNormal - else - if CheckCount < BoxCount then - NewCheckState := csMixedNormal - else - NewCheckState := csCheckedNormal; - - Node := Node.Parent; - Result := DoChecking(Node, NewCheckState); - if Result then - begin - DoCheckClick(Node, NewCheckState); - // Recursively adjust parent of parent. - // This is already done in the function DoCheckClick() called in the above line - // We revent unnecessary upward recursion by commenting this code. - // with Node^ do - // begin - // if not (vsInitialized in Parent.States) then - // InitNode(Parent); - // if ([vsChecking, vsDisabled] * Parent.States = []) and (Parent <> FRoot) and - // (Parent.CheckType = ctTriStateCheckBox) then - // Result := CheckParentCheckState(Node, NewCheckState); - // end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ClearTempCache; - -// make sure the temporary node cache is in a reliable state - -begin - FTempNodeCache := nil; - FTempNodeCount := 0; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ColumnIsEmpty(Node: PVirtualNode; Column: TColumnIndex): Boolean; - -// Returns True if the given column is to be considered as being empty. This will usually be determined by -// descendants as the base tree implementation has not enough information to decide. - -begin - Result := True; - if Assigned(FOnGetCellIsEmpty) then - FOnGetCellIsEmpty(Self, Node, Column, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ComputeRTLOffset(ExcludeScrollBar: Boolean): Integer; - -// Computes the horizontal offset needed when all columns are automatically right aligned (in RTL bidi mode). -// ExcludeScrollBar determines if the left-hand vertical scrollbar is to be included (if visible) or not. - -var - HeaderWidth: Integer; - ScrollBarVisible: Boolean; -begin - ScrollBarVisible := (Integer(FRangeY) > ClientHeight) and (ScrollBarOptions.ScrollBars in [ssVertical, ssBoth]); - if ScrollBarVisible then - Result := GetSystemMetrics(SM_CXVSCROLL) - else - Result := 0; - - // Make everything right aligned. - HeaderWidth := FHeaderRect.Right - FHeaderRect.Left; - if Integer(FRangeX) + Result <= HeaderWidth then - Result := HeaderWidth - Integer(FRangeX); - // Otherwise take only left-hand vertical scrollbar into account. - - if ScrollBarVisible and ExcludeScrollBar then - Dec(Result, GetSystemMetrics(SM_CXVSCROLL)); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CountLevelDifference(Node1, Node2: PVirtualNode): Integer; - -// This method counts how many indentation levels the given nodes are apart. If both nodes have the same parent then the -// difference is 0 otherwise the result is basically GetNodeLevel(Node2) - GetNodeLevel(Node1), but with sign. -// If the result is negative then Node2 is less intended than Node1. - -var - Level1, Level2: Integer; - -begin - Assert(Assigned(Node1) and Assigned(Node2), 'Both nodes must be Assigned.'); - - Level1 := 0; - while Node1.Parent <> FRoot do - begin - Inc(Level1); - Node1 := Node1.Parent; - end; - - Level2 := 0; - while Node2.Parent <> FRoot do - begin - Inc(Level2); - Node2 := Node2.Parent; - end; - - Result := Level2 - Level1; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CountVisibleChildren(Node: PVirtualNode): Cardinal; - -// Returns the number of visible child nodes of the given node. - -begin - Result := 0; - - // The node's direct children... - if vsExpanded in Node.States then - begin - // ...and their children. - Node := Node.FirstChild; - while Assigned(Node) do - begin - if vsVisible in Node.States then - Inc(Result, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1))); - Node := Node.NextSibling; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CreateParams(var Params: TCreateParams); - -const - ScrollBar: array[TScrollStyle] of Cardinal = (0, WS_HSCROLL, WS_VSCROLL, WS_HSCROLL or WS_VSCROLL); - -begin - inherited CreateParams(Params); - - with Params do - begin - Style := Style or WS_CLIPCHILDREN or WS_CLIPSIBLINGS or ScrollBar[ScrollBarOptions.FScrollBars]; - if toFullRepaintOnResize in FOptions.MiscOptions then - WindowClass.style := WindowClass.style or CS_HREDRAW or CS_VREDRAW - else - WindowClass.style := WindowClass.style and not (CS_HREDRAW or CS_VREDRAW); - if FBorderStyle = bsSingle then - begin - if Ctl3D then - begin - ExStyle := ExStyle or WS_EX_CLIENTEDGE; - Style := Style and not WS_BORDER; - end - else - Style := Style or WS_BORDER; - end - else - Style := Style and not WS_BORDER; - - AddBiDiModeExStyle(ExStyle); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CreateWnd; - -// Initializes data which depends on a valid window handle. - -begin - VclStyleChanged(); // Moved here due to issue #986 - DoStateChange([tsWindowCreating]); - inherited; - DoStateChange([], [tsWindowCreating]); - - if not Assigned(FCheckImages) then - FCheckImages := CreateSystemImageSet(Self); - - if ((StyleServices.Enabled ) and (toThemeAware in TreeOptions.PaintOptions) ) then - begin - DoStateChange([tsUseThemes]); - if (toUseExplorerTheme in FOptions.PaintOptions) and IsWinVistaOrAbove then - begin - DoStateChange([tsUseExplorerTheme]); - SetWindowTheme('explorer'); - end - else - DoStateChange([], [tsUseExplorerTheme]); - end - else - DoStateChange([], [tsUseThemes, tsUseExplorerTheme]); - - // Because of the special recursion and update stopper when creating the window (or resizing it) - // we have to manually trigger the auto size calculation here. - if hsNeedScaling in FHeader.States then - FHeader.RescaleHeader; - if hoAutoResize in FHeader.Options then - FHeader.Columns.AdjustAutoSize(InvalidColumn); - - PrepareBitmaps(True, True); - - // Register tree as OLE drop target. - if not (csDesigning in ComponentState) and (toAcceptOLEDrop in FOptions.MiscOptions) then - if not (csLoading in ComponentState) then // will be done in Loaded after all inherited settings are loaded from the DFMs - RegisterDragDrop(Handle, DragManager as IDropTarget); - - UpdateScrollBars(True); - UpdateHeaderRect; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.FakeReadIdent(Reader: TReader); -begin - Assert(Reader.NextValue = vaIdent); - Reader.ReadIdent; -end; - - -procedure TBaseVirtualTree.DefineProperties(Filer: TFiler); - -// There were heavy changes in some properties during development of VT. This method helps to make migration easier -// by reading old properties manually and put them into the new properties as appropriate. -// Note: these old properties are never written again and silently disappear. -// June 2002: Meanwhile another task is done here too: working around the problem that TCollection is not streamed -// correctly when using Visual Form Inheritance (VFI). - -var - StoreIt: Boolean; - -begin - inherited; - - // The header can prevent writing columns altogether. - if FHeader.CanWriteColumns then - begin - // Check if we inherit from an ancestor form (Visual Form Inheritance). - StoreIt := Filer.Ancestor = nil; - // If there is an ancestor then save columns only if they are different to the base set. - if not StoreIt then - StoreIt := not FHeader.Columns.Equals(TBaseVirtualTree(Filer.Ancestor).FHeader.Columns); - end - else - StoreIt := False; - - Filer.DefineProperty('Columns', FHeader.ReadColumns, FHeader.WriteColumns, StoreIt); - - // #622 made old DFMs incompatible with new VTW - so the program is compiled successfully - // and then suddenly crashes at user site in runtime. - Filer.DefineProperty('CheckImageKind', FakeReadIdent, nil, false); - /// #730 removed property HintAnimation - Filer.DefineProperty('HintAnimation', FakeReadIdent, nil, false); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DetermineDropMode(const P: TPoint; var HitInfo: THitInfo; var NodeRect: TRect): TDropMode; - -// Determine the DropMode. - -var - ImageHit: Boolean; - LabelHit: Boolean; - ItemHit: Boolean; - -begin - ImageHit := HitInfo.HitPositions * [hiOnNormalIcon, hiOnStateIcon] <> []; - LabelHit := hiOnItemLabel in HitInfo.HitPositions; - ItemHit := (hiOnItem in HitInfo.HitPositions) and ((toFullRowDrag in FOptions.MiscOptions) or - (toFullRowSelect in FOptions.SelectionOptions)); - - // In report mode only direct hits of the node captions/images in the main column are accepted as hits. - if (toReportMode in FOptions.MiscOptions) and not (ItemHit or ((LabelHit or ImageHit) and - (HitInfo.HitColumn = FHeader.MainColumn))) then - HitInfo.HitNode := nil; - - if Assigned(HitInfo.HitNode) then - begin - if LabelHit or ImageHit or not (toShowDropmark in FOptions.PaintOptions) then - Result := dmOnNode - else - if ((NodeRect.Top + NodeRect.Bottom) div 2) > P.Y then - Result := dmAbove - else - Result := dmBelow; - end - else - Result := dmNowhere; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DetermineHiddenChildrenFlag(Node: PVirtualNode); - -// Update the hidden children flag of the given node. - -var - Run: PVirtualNode; - -begin - if Node.ChildCount = 0 then - begin - if vsHasChildren in Node.States then - Exclude(Node.States, vsAllChildrenHidden) - else - Include(Node.States, vsAllChildrenHidden); - end - else - begin - // Iterate through all siblings and stop when one visible is found. - Run := Node.FirstChild; - while Assigned(Run) and not IsEffectivelyVisible[Run] do - Run := Run.NextSibling; - if Assigned(Run) then - Exclude(Node.States, vsAllChildrenHidden) - else - Include(Node.States, vsAllChildrenHidden); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DetermineHiddenChildrenFlagAllNodes; - -var - Run: PVirtualNode; - -begin - Run := GetFirstNoInit(False); - while Assigned(Run) do - begin - DetermineHiddenChildrenFlag(Run); - Run := GetNextNoInit(Run); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DetermineHitPositionLTR(var HitInfo: THitInfo; Offset, Right: Integer; - Alignment: TAlignment); - -// This method determines the hit position within a node with left-to-right orientation. - -var - MainColumnHit: Boolean; - lIndent, - TextWidth, - ImageOffset: Integer; - lOffsets: TVTOffsets; -begin - MainColumnHit := HitInfo.HitColumn = FHeader.MainColumn; - GetOffsets(HitInfo.HitNode, lOffsets, ofsRightOfText, HitInfo.HitColumn); - - if (MainColumnHit and (Offset < lOffsets[ofsCheckbox])) then - begin - // Position is to the left of calculated indentation which can only happen for the main column. - // Check whether it corresponds to a button/checkbox. - if (toShowButtons in FOptions.PaintOptions) and (vsHasChildren in HitInfo.HitNode.States) then - begin - // Position of button is interpreted very generously to avoid forcing the user - // to click exactly into the 9x9 pixels area. The entire node height and one full - // indentation level is accepted as button hit. - if Offset >= lOffsets[ofsCheckbox] - Integer(FIndent) then - Include(HitInfo.HitPositions, hiOnItemButton); - if Offset > lOffsets[ofsToggleButton] then - Include(HitInfo.HitPositions, hiOnItemButtonExact); - end; - // no button hit so position is on indent - if HitInfo.HitPositions = [] then - Include(HitInfo.HitPositions, hiOnItemIndent); - end - else - begin - // The next hit positions can be: - // - on the check box - // - on the state image - // - on the normal image - // - to the left of the text area - // - on the label or - // - to the right of the text area - // (in this order). - - // In report mode no hit other than in the main column is possible. - if MainColumnHit or not (toReportMode in FOptions.MiscOptions) then - begin - if MainColumnHit and (Offset < lOffsets[ofsStateImage]) then - begin - HitInfo.HitPositions := [hiOnItem]; - if (HitInfo.HitNode.CheckType <> ctNone) then - Include(HitInfo.HitPositions, hiOnItemCheckBox); - end - else - begin - ImageOffset := lOffsets[ofsImage]; - if Offset < ImageOffset then - Include(HitInfo.HitPositions, hiOnStateIcon) - else - begin - ImageOffset := lOffsets[ofsLabel]; - if Offset < ImageOffset then - Include(HitInfo.HitPositions, hiOnNormalIcon) - else - begin - TextWidth := lOffsets[ofsRightOfText] - lOffsets[ofsText]; - // ImageOffset contains now the left border of the node label area. This is used to calculate the - // correct alignment in the column. - - // Check if the text can be aligned at all. This is only possible if there is enough room - // in the remaining text rectangle. - if TextWidth > Right - ImageOffset then - Include(HitInfo.HitPositions, hiOnItemLabel) - else - begin - case Alignment of - taCenter: - begin - lIndent := (ImageOffset + Right - TextWidth) div 2; - if Offset < lIndent then - Include(HitInfo.HitPositions, hiOnItemLeft) - else - if Offset < lIndent + TextWidth then - Include(HitInfo.HitPositions, hiOnItemLabel) - else - Include(HitInfo.HitPositions, hiOnItemRight); - end; - taRightJustify: - begin - lIndent := Right - TextWidth; - if Offset < lIndent then - Include(HitInfo.HitPositions, hiOnItemLeft) - else - Include(HitInfo.HitPositions, hiOnItemLabel); - end; - else // taLeftJustify - if Offset < ImageOffset + TextWidth then - Include(HitInfo.HitPositions, hiOnItemLabel) - else - Include(HitInfo.HitPositions, hiOnItemRight); - end; - end; - end; - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DetermineHitPositionRTL(var HitInfo: THitInfo; Offset, Right: Integer; Alignment: TAlignment); - -// This method determines the hit position within a node with right-to-left orientation. - -var - MainColumnHit: Boolean; - Run: PVirtualNode; - Indent, - TextWidth, - ImageOffset: Integer; - -begin - MainColumnHit := HitInfo.HitColumn = FHeader.MainColumn; - - // If columns are not used or the main column is hit then the tree indentation must be considered too. - if MainColumnHit then - begin - if toFixedIndent in FOptions.PaintOptions then - Dec(Right, FIndent) - else - begin - Run := HitInfo.HitNode; - while (Run.Parent <> FRoot) do - begin - Dec(Right, FIndent); - Run := Run.Parent; - end; - if toShowRoot in FOptions.PaintOptions then - Dec(Right, FIndent); - end; - end; - - if Offset >= Right then - begin - // Position is to the right of calculated indentation which can only happen for the main column. - // Check whether it corresponds to a button/checkbox. - if (toShowButtons in FOptions.PaintOptions) and (vsHasChildren in HitInfo.HitNode.States) then - begin - // Position of button is interpreted very generously to avoid forcing the user - // to click exactly into the 9x9 pixels area. The entire node height and one full - // indentation level is accepted as button hit. - if Offset <= Right + Integer(FIndent) then - Include(HitInfo.HitPositions, hiOnItemButton); - if Offset <= Right + FPlusBM.Width then - Include(HitInfo.HitPositions, hiOnItemButtonExact); - end; - // no button hit so position is on indent - if HitInfo.HitPositions = [] then - Include(HitInfo.HitPositions, hiOnItemIndent); - end - else - begin - // The next hit positions can be: - // - on the check box - // - on the state image - // - on the normal image - // - to the left of the text area - // - on the label or - // - to the right of the text area - // (in this order). - - // In report mode no hit other than in the main column is possible. - if MainColumnHit or not (toReportMode in FOptions.MiscOptions) then - begin - ImageOffset := Right - FMargin; - - // Check support is only available for the main column. - if MainColumnHit and (toCheckSupport in FOptions.MiscOptions) and Assigned(FCheckImages) and - (HitInfo.HitNode.CheckType <> ctNone) then - Dec(ImageOffset, FCheckImages.Width + FImagesMargin); - - if MainColumnHit and (Offset > ImageOffset) then - begin - HitInfo.HitPositions := [hiOnItem]; - if (HitInfo.HitNode.CheckType <> ctNone) then - Include(HitInfo.HitPositions, hiOnItemCheckBox); - end - else - begin - Dec(ImageOffset, GetImageSize(HitInfo.HitNode, ikState, HitInfo.HitColumn).cx); - if Offset > ImageOffset then - Include(HitInfo.HitPositions, hiOnStateIcon) - else - begin - Dec(ImageOffset, GetImageSize(HitInfo.HitNode, ikNormal, HitInfo.HitColumn).cx); - if Offset > ImageOffset then - Include(HitInfo.HitPositions, hiOnNormalIcon) - else - begin - // ImageOffset contains now the right border of the node label area. This is used to calculate the - // correct alignment in the column. - TextWidth := DoGetNodeWidth(HitInfo.HitNode, HitInfo.HitColumn); - - // Check if the text can be aligned at all. This is only possible if there is enough room - // in the remaining text rectangle. - if TextWidth > ImageOffset then - Include(HitInfo.HitPositions, hiOnItemLabel) - else - begin - // Consider bidi mode here. In RTL context does left alignment actually mean right alignment - // and vice versa. - ChangeBiDiModeAlignment(Alignment); - - case Alignment of - taCenter: - begin - Indent := (ImageOffset - TextWidth) div 2; - if Offset < Indent then - Include(HitInfo.HitPositions, hiOnItemLeft) - else - if Offset < Indent + TextWidth then - Include(HitInfo.HitPositions, hiOnItemLabel) - else - Include(HitInfo.HitPositions, hiOnItemRight); - end; - taRightJustify: - begin - Indent := ImageOffset - TextWidth; - if Offset < Indent then - Include(HitInfo.HitPositions, hiOnItemLeft) - else - Include(HitInfo.HitPositions, hiOnItemLabel); - end; - else // taLeftJustify - if Offset > TextWidth then - Include(HitInfo.HitPositions, hiOnItemRight) - else - Include(HitInfo.HitPositions, hiOnItemLabel); - end; - end; - end; - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DetermineLineImageAndSelectLevel(Node: PVirtualNode; var LineImage: TLineImage): Integer; - -// This method is used during paint cycles and initializes an array of line type IDs. These IDs are used to paint -// the tree lines in front of the given node. -// Additionally an initial count of selected parents is determined and returned which is used for specific painting. - -var - X: Integer; - Indent: Integer; - Run: PVirtualNode; - -begin - Result := 0; - if toShowRoot in FOptions.PaintOptions then - X := 1 - else - X := 0; - Run := Node; - // Determine indentation level of top node. - while Run.Parent <> FRoot do - begin - Inc(X); - Run := Run.Parent; - // Count selected nodes (FRoot is never selected). - if vsSelected in Run.States then - Inc(Result); - end; - - // Set initial size of line index array, this will automatically initialized all entries to ltNone. - SetLength(LineImage, X); - Indent := X - 1; - - // Only use lines if requested. - if (toShowTreeLines in FOptions.PaintOptions) and - (not (toHideTreeLinesIfThemed in FOptions.PaintOptions) or not (tsUseThemes in FStates)) then - begin - if toChildrenAbove in FOptions.PaintOptions then - begin - Dec(X); - if not HasVisiblePreviousSibling(Node) then - begin - if (Node.Parent <> FRoot) or HasVisibleNextSibling(Node) then - LineImage[X] := ltBottomRight - else - LineImage[X] := ltRight; - end - else - if (Node.Parent = FRoot) and (not HasVisibleNextSibling(Node)) then - LineImage[X] := ltTopRight - else - LineImage[X] := ltTopDownRight; - - // Now go up to the root to determine the rest. - Run := Node.Parent; - while Run <> FRoot do - begin - Dec(X); - if HasVisiblePreviousSibling(Run) then - LineImage[X] := ltTopDown - else - LineImage[X] := ltNone; - - Run := Run.Parent; - end; - end - else - begin - // Start over parent traversal if necessary. - Run := Node; - - if Run.Parent <> FRoot then - begin - // The very last image (the one immediately before the item label) is different. - if HasVisibleNextSibling(Run) then - LineImage[X - 1] := ltTopDownRight - else - LineImage[X - 1] := ltTopRight; - Run := Run.Parent; - - // Now go up all parents. - repeat - if Run.Parent = FRoot then - Break; - Dec(X); - if HasVisibleNextSibling(Run) then - LineImage[X - 1] := ltTopDown - else - LineImage[X - 1] := ltNone; - Run := Run.Parent; - until False; - end; - - // Prepare root level. Run points at this stage to a top level node. - if (toShowRoot in FOptions.PaintOptions) and ((toShowTreeLines in FOptions.PaintOptions) and - (not (toHideTreeLinesIfThemed in FOptions.PaintOptions) or not (tsUseThemes in FStates))) then - begin - // Is the top node a root node? - if Run = Node then - begin - // First child gets the bottom-right bitmap if it isn't also the only child. - if IsFirstVisibleChild(FRoot, Run) then - // Is it the only child? - if IsLastVisibleChild(FRoot, Run) then - LineImage[0] := ltRight - else - LineImage[0] := ltBottomRight - else - // real last child - if IsLastVisibleChild(FRoot, Run) then - LineImage[0] := ltTopRight - else - LineImage[0] := ltTopDownRight; - end - else - begin - // No, top node is not a top level node. So we need different painting. - if HasVisibleNextSibling(Run) then - LineImage[0] := ltTopDown - else - LineImage[0] := ltNone; - end; - end; - end; - end; - - if (tsUseExplorerTheme in FStates) and HasChildren[Node] and (Indent >= 0) - and not ((vsAllChildrenHidden in Node.States) and (toAutoHideButtons in TreeOptions.AutoOptions)) then - LineImage[Indent] := ltNone; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DetermineNextCheckState(CheckType: TCheckType; CheckState: TCheckState): TCheckState; - -// Determines the next check state in case the user click the check image or pressed the space key. - -begin - case CheckType of - ctTriStateCheckBox, - ctButton, - ctCheckBox: - begin - Result := CheckState.GetToggled(); - end;//ctCheckbox - ctRadioButton: - Result := csCheckedNormal; - else - Result := csMixedNormal; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DetermineScrollDirections(X, Y: Integer): TScrollDirections; - -// Determines which direction the client area must be scrolled depending on the given position. - -begin - Result:= []; - - if CanAutoScroll then - begin - // Calculation for wheel panning/scrolling is a bit different to normal auto scroll. - if [tsWheelPanning, tsWheelScrolling] * FStates <> [] then - begin - if (X - FLastClickPos.X) < -8 then - Include(Result, sdLeft); - if (X - FLastClickPos.X) > 8 then - Include(Result, sdRight); - - if (Y - FLastClickPos.Y) < -8 then - Include(Result, sdUp); - if (Y - FLastClickPos.Y) > 8 then - Include(Result, sdDown); - end - else - begin - if (X < Integer(FDefaultNodeHeight)) and (FEffectiveOffsetX <> 0) then - Include(Result, sdLeft); - if (ClientWidth + FEffectiveOffsetX < Integer(FRangeX)) and (X > ClientWidth - Integer(FDefaultNodeHeight)) then - Include(Result, sdRight); - - if (Y < Integer(FDefaultNodeHeight)) and (FOffsetY <> 0) then - Include(Result, sdUp); - if (ClientHeight - FOffsetY < Integer(FRangeY)) and (Y > ClientHeight - Integer(FDefaultNodeHeight)) then - Include(Result, sdDown); - - // Since scrolling during dragging is not handled via the timer we do a check here whether the auto - // scroll timeout already has elapsed or not. - if (Result <> []) and - ((Assigned(FDragManager) and DragManager.IsDropTarget) or - (FindDragTarget(Point(X, Y), False) = Self)) then - begin - if FDragScrollStart = 0 then - FDragScrollStart := timeGetTime; - // Reset any scroll direction to avoid scroll in the case the user is dragging and the auto scroll time has not - // yet elapsed. - if ((Int64(timeGetTime) - FDragScrollStart) < FAutoScrollDelay) then - Result := []; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoAdvancedHeaderDraw(var PaintInfo: THeaderPaintInfo; const Elements: THeaderPaintElements); - -begin - if Assigned(FOnAdvancedHeaderDraw) then - FOnAdvancedHeaderDraw(FHeader, PaintInfo, Elements); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoAfterCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect); - -begin - if Assigned(FOnAfterCellPaint) then - FOnAfterCellPaint(Self, Canvas, Node, Column, CellRect); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoAfterItemErase(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect); - -begin - if Assigned(FOnAfterItemErase) then - FOnAfterItemErase(Self, Canvas, Node, ItemRect); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoAfterItemPaint(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect); - -begin - if Assigned(FOnAfterItemPaint) then - FOnAfterItemPaint(Self, Canvas, Node, ItemRect); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoAfterPaint(Canvas: TCanvas); - -begin - if Assigned(FOnAfterPaint) then - FOnAfterPaint(Self, Canvas); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoAutoScroll(X, Y: Integer); - -begin - FScrollDirections := DetermineScrollDirections(X, Y); - - if FStates * [tsWheelPanning, tsWheelScrolling] = [] then - begin - if FScrollDirections = [] then - begin - if ((FStates * [tsScrollPending, tsScrolling]) <> []) then - begin - StopTimer(ScrollTimer); - DoStateChange([], [tsScrollPending, tsScrolling]); - end; - end - else - begin - // start auto scroll if not yet done - if (FStates * [tsScrollPending, tsScrolling]) = [] then - begin - DoStateChange([tsScrollPending]); - SetTimer(Handle, ScrollTimer, FAutoScrollDelay, nil); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoBeforeDrag(Node: PVirtualNode; Column: TColumnIndex): Boolean; - -begin - Result := False; - if Assigned(FOnDragAllowed) then - FOnDragAllowed(Self, Node, Column, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoBeforeCellPaint(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; - CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); - -var - UpdateRect: TRect; - -begin - if Assigned(FOnBeforeCellPaint) then - begin - if CellPaintMode = cpmGetContentMargin then - begin - // Prevent drawing if we are only about to get the margin. As this also clears the update rect we need to save it. - GetUpdateRect(Handle, UpdateRect, False); - SetUpdateState(True); - end; - - Canvas.Font := Self.Font; // Fixes issue #298 - FOnBeforeCellPaint(Self, Canvas, Node, Column, CellPaintMode, CellRect, ContentRect); - - if CellPaintMode = cpmGetContentMargin then - SetUpdateState(False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoBeforeItemErase(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect; var Color: TColor; - var EraseAction: TItemEraseAction); - -begin - if Assigned(FOnBeforeItemErase) then - FOnBeforeItemErase(Self, Canvas, Node, ItemRect, Color, EraseAction); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoBeforeItemPaint(Canvas: TCanvas; Node: PVirtualNode; ItemRect: TRect): Boolean; - -begin - // By default custom draw will not be used, so the tree handles drawing the node. - Result := False; - if Assigned(FOnBeforeItemPaint) then - FOnBeforeItemPaint(Self, Canvas, Node, ItemRect, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoBeforePaint(Canvas: TCanvas); - -begin - if Assigned(FOnBeforePaint) then - FOnBeforePaint(Self, Canvas); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoCancelEdit(): Boolean; - -// Called when the current edit action or a pending edit must be cancelled. - -begin - StopTimer(EditTimer); - DoStateChange([], [tsEditPending]); - Result := (tsEditing in FStates) and FEditLink.CancelEdit; - if Result then - begin - DoStateChange([], [tsEditing]); - if Assigned(FOnEditCancelled) then - FOnEditCancelled(Self, FEditColumn); - if not (csDestroying in ComponentState) then - FEditLink := nil; - TrySetFocus(); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoCanEdit(Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); - -begin - if Assigned(FOnEditing) then - FOnEditing(Self, Node, Column, Allowed); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoCanSplitterResizeNode(P: TPoint; Node: PVirtualNode; Column: TColumnIndex; - var Allowed: Boolean); - -begin - if Assigned(FOnCanSplitterResizeNode) then - FOnCanSplitterResizeNode(Self, P, Node, Column, Allowed); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoChange(Node: PVirtualNode); - -begin - StopTimer(ChangeTimer); - if Assigned(FOnChange) then - FOnChange(Self, Node); - - // This is a good place to reset the cached node. This is the same as the node passed in here. - // This is necessary to allow descendants to override this method and get the node then. - DoStateChange([], [tsChangePending]); - FLastChangedNode := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoCheckClick(Node: PVirtualNode; NewCheckState: TCheckState); - -begin - if ChangeCheckState(Node, NewCheckState) then - begin - DoChecked(Node); - if SyncCheckstateWithSelection[Node] then - begin - // selection should follow check state - if (NewCheckState = csCheckedNormal) then - Selected[node] := true - else - Selected[node] := false; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoChecked(Node: PVirtualNode); - -begin - if Assigned(FOnChecked) then - FOnChecked(Self, Node); - if Assigned(FAccessibleItem) and (Self.UpdateCount = 0) then // See issue #1174 - NotifyWinEvent(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoChecking(Node: PVirtualNode; var NewCheckState: TCheckState): Boolean; - -// Determines if a node is allowed to change its check state to NewCheckState. - -begin - if (toReadOnly in FOptions.MiscOptions) or (vsDisabled in Node.States) then - Result := False - else - begin - Result := True; - if Assigned(FOnChecking) then - FOnChecking(Self, Node, NewCheckState, Result); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoCollapsed(Node: PVirtualNode); -var - lFirstSelected: PVirtualNode; - lParent: PVirtualNode; -begin - if Assigned(FOnCollapsed) then - FOnCollapsed(Self, Node); - - if Assigned(FAccessibleItem) and (Self.UpdateCount = 0) then // See issue #1174 - NotifyWinEvent(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF); - - if (toAlwaysSelectNode in TreeOptions.SelectionOptions) then - begin - // Select the next visible parent if the currently selected node gets invisible due to a collapse - // This makes the VT behave more like the Win32 custom TreeView control - // This makes only sense no no multi selection is allowed and if there is a selected node at all - lFirstSelected := GetFirstSelected(); - if Assigned(lFirstSelected) and not FullyVisible[lFirstSelected] then - begin - lParent := GetVisibleParent(lFirstSelected); - Selected[lFirstSelected] := False; - Selected[lParent] := True; - end;//if - //if there is (still) no selected node, then use FNextNodeToSelect to select one - if SelectedCount = 0 then - EnsureNodeSelected(); - end;//if -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoCollapsing(Node: PVirtualNode): Boolean; - -begin - Result := True; - if Assigned(FOnCollapsing) then - FOnCollapsing(Self, Node, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoColumnClick(Column: TColumnIndex; Shift: TShiftState); - -begin - if Assigned(FOnColumnClick) then - FOnColumnClick(Self, Column, Shift); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoColumnDblClick(Column: TColumnIndex; Shift: TShiftState); - -begin - if Assigned(FOnColumnDblClick) then - FOnColumnDblClick(Self, Column, Shift); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoColumnResize(Column: TColumnIndex); - -var - R: TRect; - Run: PVirtualNode; - -begin - if not (csLoading in ComponentState) and HandleAllocated then -begin - // Reset all vsHeightMeasured flags if we are in multiline mode. - Run := GetFirstInitialized; - while Assigned(Run) do - begin - if vsMultiline in Run.States then - Exclude(Run.States, vsHeightMeasured); - Run := GetNextInitialized(Run); - end; - - UpdateHorizontalScrollBar(True); - if Column > NoColumn then - begin - // Invalidate client area from the current column all to the right (or left in RTL mode). - R := ClientRect; - if not (toAutoSpanColumns in FOptions.AutoOptions) then - if UseRightToLeftAlignment then - R.Right := FHeader.Columns[Column].Left + FHeader.Columns[Column].Width + ComputeRTLOffset - else - R.Left := FHeader.Columns[Column].Left; - InvalidateRect(Handle, @R, False); - FHeader.Invalidate(FHeader.Columns[Column], True); - end; - if [hsColumnWidthTracking, hsResizing] * FHeader.States = [hsColumnWidthTracking] then - UpdateWindow(Handle); - - if not (IsUpdating) then - UpdateDesigner; // design time only - - if Assigned(FOnColumnResize) and not (hsResizing in FHeader.States) then - FOnColumnResize(FHeader, Column); - - // If the tree is currently in edit state then notify edit link. - if tsEditing in FStates then - UpdateEditBounds; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoColumnVisibilityChanged(const Column: TColumnIndex; Visible: Boolean); - // Triggers the OnColumnVisibilityChanged event. -begin - if Assigned(OnColumnVisibilityChanged) then - OnColumnVisibilityChanged(Self, Column, Visible); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoCompare(Node1, Node2: PVirtualNode; Column: TColumnIndex): Integer; - -begin - Result := 0; - if Assigned(FOnCompareNodes) then - FOnCompareNodes(Self, Node1, Node2, Column, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoCreateDataObject: IDataObject; - -begin - Result := nil; - if Assigned(FOnCreateDataObject) then - FOnCreateDataObject(Self, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoCreateDragManager: IVTDragManager; - -begin - Result := nil; - if Assigned(FOnCreateDragManager) then - FOnCreateDragManager(Self, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; - -begin - Result := nil; - if Assigned(FOnCreateEditor) then - FOnCreateEditor(Self, Node, Column, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoDragging(P: TPoint); - -// Initiates finally the drag'n drop operation and returns after DD is finished. - - //--------------- local function -------------------------------------------- - - function GetDragOperations: Integer; - - begin - if FDragOperations = [] then - Result := DROPEFFECT_COPY or DROPEFFECT_MOVE or DROPEFFECT_LINK - else - begin - Result := 0; - if doCopy in FDragOperations then - Result := Result or DROPEFFECT_COPY; - if doLink in FDragOperations then - Result := Result or DROPEFFECT_LINK; - if doMove in FDragOperations then - Result := Result or DROPEFFECT_MOVE; - end; - end; - - //--------------- end local function ---------------------------------------- - -var - AllowedEffects: Integer; - DragObject: TDragObject; - - DataObject: IDataObject; - -begin - DataObject := nil; - // Dragging is dragging, nothing else. - DoCancelEdit; - - if Assigned(FCurrentHotNode) then - begin - InvalidateNode(FCurrentHotNode); - FCurrentHotNode := nil; - end; - // Select the focused node if not already done. - if Assigned(FFocusedNode) and not (vsSelected in FFocusedNode.States) then - begin - InternalAddToSelection(FFocusedNode, False); - InvalidateNode(FFocusedNode); - end; - - UpdateWindow(Handle); - - // Keep a list of all currently selected nodes as this list might change, - // but we have probably to delete currently selected nodes. - FDragSelection := GetSortedSelection(True); - try - DoStateChange([tsOLEDragging], [tsOLEDragPending, tsClearPending]); - - // An application might create a drag object like used during VCL dd. This is not required for OLE dd but - // required as parameter. - DragObject := nil; - DoStartDrag(DragObject); - DragObject.Free; - - DataObject := DragManager.DataObject; - PrepareDragImage(P, DataObject); - - FLastDropMode := dmOnNode; - // Don't forget to initialize the result. It might never be touched. - FLastDragEffect := DROPEFFECT_NONE; - AllowedEffects := GetDragOperations; - try - DragAndDrop(AllowedEffects, DataObject, FLastDragEffect); - DragManager.ForceDragLeave; - finally - GetCursorPos(P); - P := ScreenToClient(P); - DoEndDrag(Self, P.X, P.Y); - - FDragImage.EndDrag; - - // Finish the operation. - if (FLastDragEffect = DROPEFFECT_MOVE) and (toAutoDeleteMovedNodes in TreeOptions.AutoOptions) then - begin - // The operation was a move so delete the previously selected nodes. - DeleteSelectedNodes; - end; - - DoStateChange([], [tsOLEDragging]); - end; - finally - FDragSelection := nil; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoDragExpand; - -var - SourceTree: TBaseVirtualTree; - -begin - StopTimer(ExpandTimer); - if Assigned(FDropTargetNode) and (vsHasChildren in FDropTargetNode.States) and - not (vsExpanded in FDropTargetNode.States) then - begin - if Assigned(FDragManager) then - SourceTree := DragManager.DragSource - else - SourceTree := nil; - - if not DragManager.DropTargetHelperSupported and Assigned(SourceTree) then - SourceTree.FDragImage.HideDragImage; - ToggleNode(FDropTargetNode); - UpdateWindow(Handle); - if not DragManager.DropTargetHelperSupported and Assigned(SourceTree) then - SourceTree.FDragImage.ShowDragImage; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoDragOver(Source: TObject; Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode; - var Effect: Integer): Boolean; - -begin - Result := False; - if Assigned(FOnDragOver) then - FOnDragOver(Self, Source, Shift, State, Pt, Mode, Effect, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoDragDrop(Source: TObject; const DataObject: IDataObject; const Formats: TFormatArray; - Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode); - -begin - if Assigned(FOnDragDrop) then - FOnDragDrop(Self, Source, DataObject, Formats, Shift, Pt, Effect, Mode); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoBeforeDrawLineImage(Node: PVirtualNode; Level: Integer; var XPos: Integer); - -begin - if Assigned(FOnBeforeDrawLineImage) then - FOnBeforeDrawLineImage(Self, Node, Level, XPos); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoEdit; - -begin - Application.CancelHint; - StopTimer(ScrollTimer); - StopTimer(EditTimer); - DoStateChange([], [tsEditPending]); - if Assigned(FFocusedNode) and not (vsDisabled in FFocusedNode.States) and - not (toReadOnly in FOptions.MiscOptions) and (FEditLink = nil) then - begin - ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions, not (toDisableAutoscrollOnEdit in FOptions.AutoOptions)); - FEditLink := DoCreateEditor(FFocusedNode, FEditColumn); - if Assigned(FEditLink) then - begin - DoStateChange([tsEditing], [tsDrawSelecting, tsDrawSelPending, tsToggleFocusedSelection, tsOLEDragPending, - tsOLEDragging, tsClearPending, tsDrawSelPending, tsScrollPending, tsScrolling]); - if FEditLink.PrepareEdit(Self, FFocusedNode, FEditColumn) then - begin - UpdateEditBounds; - // Node needs repaint because the selection rectangle and static text must disappear. - InvalidateNode(FFocusedNode); - if not FEditLink.BeginEdit then - DoStateChange([], [tsEditing]); - end - else - DoStateChange([], [tsEditing]); - if not (tsEditing in FStates) then - FEditLink := nil; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoEndDrag(Target: TObject; X, Y: Integer); - -// Does some housekeeping for VCL drag'n drop; - -begin - inherited; - - DragFinished; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoEndEdit: Boolean; - -// Called to finish a current edit action or stop the edit timer if an edit operation is pending. -// Returns True if editing was successfully ended or the control was not in edit mode -// Returns False if the control could not leave the edit mode e.g. due to an invalid value that was entered. - -begin - StopTimer(EditTimer); - Result := (tsEditing in FStates) and FEditLink.EndEdit; - if Result then - begin - DoStateChange([], [tsEditing]); - FEditLink := nil; - if Assigned(FOnEdited) then - FOnEdited(Self, FFocusedNode, FEditColumn); - end; - DoStateChange([], [tsEditPending]); - TrySetFocus(); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoEndOperation(OperationKind: TVTOperationKind); - -begin - if Assigned(FOnEndOperation) then - FOnEndOperation(Self, OperationKind); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoEnter(); -begin - inherited; - EnsureNodeSelected(); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoExpanded(Node: PVirtualNode); - -begin - if Assigned(FOnExpanded) then - FOnExpanded(Self, Node); - - if Assigned(FAccessibleItem) and (Self.UpdateCount = 0) then // See issue #1174 - NotifyWinEvent(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoExpanding(Node: PVirtualNode): Boolean; - -begin - Result := True; - if Assigned(FOnExpanding) then - FOnExpanding(Self, Node, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoFocusChange(Node: PVirtualNode; Column: TColumnIndex); - -begin - if Assigned(FOnFocusChanged) then - FOnFocusChanged(Self, Node, Column); - - if Assigned(FAccessibleItem) then - begin - NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE, Handle, OBJID_CLIENT, CHILDID_SELF); - NotifyWinEvent(EVENT_OBJECT_NAMECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF); - NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF); - NotifyWinEvent(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF); - NotifyWinEvent(EVENT_OBJECT_SELECTION, Handle, OBJID_CLIENT, CHILDID_SELF); - NotifyWinEvent(EVENT_OBJECT_FOCUS, Handle, OBJID_CLIENT, CHILDID_SELF); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoFocusChanging(OldNode, NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex): Boolean; - -begin - Result := (OldColumn = NewColumn) or FHeader.AllowFocus(NewColumn); - if Assigned(FOnFocusChanging) then - FOnFocusChanging(Self, OldNode, NewNode, OldColumn, NewColumn, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoFocusNode(Node: PVirtualNode; Ask: Boolean); - -begin - if not (tsEditing in FStates) or EndEditNode then - begin - if Node = FRoot then - Node := nil; - if (FFocusedNode <> Node) and (not Ask or DoFocusChanging(FFocusedNode, Node, FFocusedColumn, FFocusedColumn)) then - begin - if Assigned(FFocusedNode) then - begin - // Do automatic collapsing of last focused node if enabled. This is however only done if - // old and new focused node have a common parent node. - if (toAutoExpand in FOptions.AutoOptions) and Assigned(Node) and (Node.Parent = FFocusedNode.Parent) and - (vsExpanded in FFocusedNode.States) then - ToggleNode(FFocusedNode) - else - InvalidateNode(FFocusedNode); - end; - FFocusedNode := Node; - end; - - // Have to scroll the node into view, even it is the same node as before. - if Assigned(FFocusedNode) then - begin - // Make sure a valid column is set if columns are used and no column has currently the focus. - if FHeader.UseColumns and (not FHeader.Columns.IsValidColumn(FFocusedColumn)) then - FFocusedColumn := FHeader.MainColumn; - // Do automatic expansion of the newly focused node if enabled. - if (toAutoExpand in FOptions.AutoOptions) and not (vsExpanded in FFocusedNode.States) then - ToggleNode(FFocusedNode); - InvalidateNode(FFocusedNode); - if (FUpdateCount = 0) and not (toDisableAutoscrollOnFocus in FOptions.AutoOptions) then - ScrollIntoView(FFocusedNode, (toCenterScrollIntoView in FOptions.SelectionOptions) and - (MouseButtonDown * FStates = []), not (toFullRowSelect in FOptions.SelectionOptions) ); - end; - - // Reset range anchor if necessary. - if FSelectionCount = 0 then - ResetRangeAnchor; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoFreeNode(Node: PVirtualNode); - -var - IntfData: IInterface; -begin - // Prevent invalid references - if Node = FLastChangedNode then - FLastChangedNode := nil; - if Node = FCurrentHotNode then - FCurrentHotNode := nil; - if Node = FDropTargetNode then - FDropTargetNode := nil; - if Node = FLastStructureChangeNode then - FLastStructureChangeNode := nil; - if Node = FFocusedNode then - FFocusedNode := nil; - if Node = FNextNodeToSelect then - UpdateNextNodeToSelect(Node); - if Node = FLastHitInfo.HitNode then - FLastHitInfo.HitNode := nil; - // fire event - if Assigned(FOnFreeNode) and ([vsInitialized, vsOnFreeNodeCallRequired] * Node.States <> []) then - FOnFreeNode(Self, Node); - - if vsReleaseCallOnUserDataRequired in Node.States then - begin - // Data may have been set to nil, in which case we can't call _Release on it - IntfData := GetInterfaceFromNodeData(Node); - if Assigned(IntfData) then - IntfData._Release(); - end; - - FreeMem(Node); - if Self.UpdateCount = 0 then - EnsureNodeSelected(); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex; - CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; - -// Determines the margins of the content rectangle caused by DoBeforeCellPaint. -// Note that shrinking the content rectangle results in positive margins whereas enlarging the content rectangle results -// in negative margins. - -var - CellRect, - ContentRect: TRect; - -begin - Result := Point(0, 0); - - if Assigned(FOnBeforeCellPaint) then // Otherwise DoBeforeCellPaint has no effect. - begin - if Canvas = nil then - Canvas := Self.Canvas; - - // Determine then node's cell rectangle and content rectangle before calling DoBeforeCellPaint. - CellRect := GetDisplayRect(Node, Column, True); - ContentRect := CellRect; - DoBeforeCellPaint(Canvas, Node, Column, cpmGetContentMargin, CellRect, ContentRect); - - // Calculate the changes caused by DoBeforeCellPaint. - case CellContentMarginType of - ccmtAllSides: - // Calculate the width difference and high difference. - Result := Point((CellRect.Right - CellRect.Left) - (ContentRect.Right - ContentRect.Left), - (CellRect.Bottom - CellRect.Top) - (ContentRect.Bottom - ContentRect.Top)); - ccmtTopLeftOnly: - // Calculate the left margin and top margin only. - Result := Point(ContentRect.Left - CellRect.Left, ContentRect.Top - CellRect.Top); - ccmtBottomRightOnly: - // Calculate the right margin and bottom margin only. - Result := Point(CellRect.Right - ContentRect.Right, CellRect.Bottom - ContentRect.Bottom); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoGetCursor(var Cursor: TCursor); - -begin - if Assigned(FOnGetCursor) then - FOnGetCursor(Self, Cursor); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoGetHeaderCursor(var Cursor: HCURSOR); - -begin - if Assigned(FOnGetHeaderCursor) then - FOnGetHeaderCursor(FHeader, Cursor); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoGetImageIndex(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; - var Ghosted: Boolean; var Index: TImageIndex): TCustomImageList; - -// Queries the application/descendant about certain image properties for a node. -// Returns a custom image list if given by the callee, otherwise nil. -const - cTVTImageKind2String: Array [TVTImageKind] of string = ('ikNormal', 'ikSelected', 'ikState', 'ikOverlay'); -begin - if (Kind = ikState) and Assigned(StateImages) then - Result := Self.StateImages - else - Result := Self.Images; - // First try the enhanced event to allow for custom image lists. - if Assigned(FOnGetImageEx) then - FOnGetImageEx(Self, Node, Kind, Column, Ghosted, Index, Result) - else if Assigned(FOnGetImage) then - FOnGetImage(Self, Node, Kind, Column, Ghosted, Index); - - Assert((Index < 0) or Assigned(Result), 'An image index was supplied for TVTImageKind.' + cTVTImageKind2String[Kind] + ' but no image list was supplied.'); - if not Assigned(Result) then - Index := -1; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoGetImageText(Node: PVirtualNode; Kind: TVTImageKind; - Column: TColumnIndex; var ImageText: string); - -// Queries the application/descendant about alternative image text for a node. - -begin - if Assigned(FOnGetImageText) then - FOnGetImageText(Self, Node, Kind, Column, ImageText); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoGetLineStyle(var Bits: Pointer); - -begin - if Assigned(FOnGetLineStyle) then - FOnGetLineStyle(Self, Bits); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoGetNodeHint(Node: PVirtualNode; Column: TColumnIndex; - var LineBreakStyle: TVTTooltipLineBreakStyle): string; - -begin - Result := Hint; - LineBreakStyle := hlbDefault; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoGetNodeTooltip(Node: PVirtualNode; Column: TColumnIndex; - var LineBreakStyle: TVTTooltipLineBreakStyle): string; - -begin - Result := Hint; - LineBreakStyle := hlbDefault; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoGetNodeExtraWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; - -// Returns the pixel width of extra space occupied by node contents (for example, static text). - -begin - Result := 0; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; - -// Returns the pixel width of a node. - -begin - Result := 0; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoGetPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Position: TPoint): TPopupMenu; - -// Queries the application whether there is a node specific popup menu. - -var - Run: PVirtualNode; - AskParent: Boolean; - -begin - Result := nil; - if Assigned(FOnGetPopupMenu) then - begin - Run := Node; - - if Assigned(Run) then - begin - AskParent := True; - repeat - FOnGetPopupMenu(Self, Run, Column, Position, AskParent, Result); - Run := Run.Parent; - until (Run = FRoot) or Assigned(Result) or not AskParent; - end - else - FOnGetPopupMenu(Self, nil, NoColumn, Position, AskParent, Result); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoGetUserClipboardFormats(var Formats: TFormatEtcArray); - -begin - if Assigned(FOnGetUserClipboardFormats) then - FOnGetUserClipboardFormats(Self, Formats); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHeaderAddPopupItem(const Column: TColumnIndex; var Cmd: TAddPopupItemType); - -begin - if Assigned(FOnHeaderAddPopupItem) then - FOnHeaderAddPopupItem(Self, Column, Cmd); - -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHeaderClick(const HitInfo: TVTHeaderHitInfo); - -begin - if Assigned(FOnHeaderClick) then - FOnHeaderClick(FHeader, HitInfo); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHeaderDblClick(const HitInfo: TVTHeaderHitInfo); - -begin - if Assigned(FOnHeaderDblClick) then - FOnHeaderDblClick(FHeader, HitInfo); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHeaderDragged(Column: TColumnIndex; OldPosition: TColumnPosition); - -begin - if Assigned(FOnHeaderDragged) then - FOnHeaderDragged(FHeader, Column, OldPosition); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHeaderDraggedOut(Column: TColumnIndex; DropPosition: TPoint); - -begin - if Assigned(FOnHeaderDraggedOut) then - FOnHeaderDraggedOut(FHeader, Column, DropPosition); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoHeaderDragging(Column: TColumnIndex): Boolean; - -begin - Result := True; - if Assigned(FOnHeaderDragging) then - FOnHeaderDragging(FHeader, Column, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHeaderDraw(Canvas: TCanvas; Column: TVirtualTreeColumn; R: TRect; Hover, Pressed: Boolean; - DropMark: TVTDropMarkMode); - -begin - if Assigned(FOnHeaderDraw) then - FOnHeaderDraw(FHeader, Canvas, Column, R, Hover, Pressed, DropMark); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHeaderDrawQueryElements(var PaintInfo: THeaderPaintInfo; var Elements: THeaderPaintElements); - -begin - if Assigned(FOnHeaderDrawQueryElements) then - FOnHeaderDrawQueryElements(FHeader, PaintInfo, Elements); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHeaderMouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); - -begin - if Assigned(FOnHeaderMouseDown) then - FOnHeaderMouseDown(FHeader, Button, Shift, X, Y); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHeaderMouseMove(Shift: TShiftState; X, Y: Integer); - -begin - if Assigned(FOnHeaderMouseMove) then - FOnHeaderMouseMove(FHeader, Shift, X, Y); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHeaderMouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); - -begin - if Assigned(FOnHeaderMouseUp) then - FOnHeaderMouseUp(FHeader, Button, Shift, X, Y); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoHotChange(Old, New: PVirtualNode); - -begin - if Assigned(FOnHotChange) then - FOnHotChange(Self, Old, New); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoIncrementalSearch(Node: PVirtualNode; const Text: string): Integer; - -begin - Result := 0; - if Assigned(FOnIncrementalSearch) then - FOnIncrementalSearch(Self, Node, Text, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoInitChildren(Node: PVirtualNode; var ChildCount: Cardinal): Boolean; -/// The function calls the OnInitChildren and returns True if the event was called; it returns False if the caller can expect that no changes have been made to ChildCount -begin - if Assigned(FOnInitChildren) then - begin - FOnInitChildren(Self, Node, ChildCount); - Result := True; - end - else - Result := False; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoInitNode(Parent, Node: PVirtualNode; var InitStates: TVirtualNodeInitStates); - -begin - if Assigned(FOnInitNode) then - FOnInitNode(Self, Parent, Node, InitStates); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoKeyAction(var CharCode: Word; var Shift: TShiftState): Boolean; - -begin - Result := True; - if Assigned(FOnKeyAction) then - FOnKeyAction(Self, CharCode, Shift, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoLoadUserData(Node: PVirtualNode; Stream: TStream); - -begin - if Assigned(FOnLoadNode) then - if Node = FRoot then - FOnLoadNode(Self, nil, Stream) - else - FOnLoadNode(Self, Node, Stream); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoMeasureItem(TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer); - -begin - if not (vsInitialized in Node.States) then - InitNode(Node); - if Assigned(FOnMeasureItem) then - FOnMeasureItem(Self, TargetCanvas, Node, NodeHeight); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoMouseEnter(); - -begin - if Assigned(FOnMouseEnter) then - FOnMouseEnter(Self); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoMouseLeave; - -begin - if Assigned(FOnMouseLeave) then - FOnMouseLeave(Self); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoNodeCopied(Node: PVirtualNode); - -begin - if Assigned(FOnNodeCopied) then - FOnNodeCopied(Self, Node); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoNodeCopying(Node, NewParent: PVirtualNode): Boolean; - -begin - Result := True; - if Assigned(FOnNodeCopying) then - FOnNodeCopying(Self, Node, NewParent, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoNodeClick(const HitInfo: THitInfo); - -begin - if Assigned(FOnNodeClick) then - FOnNodeClick(Self, HitInfo); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoNodeDblClick(const HitInfo: THitInfo); - -begin - if Assigned(FOnNodeDblClick) then - FOnNodeDblClick(Self, HitInfo); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoNodeHeightDblClickResize(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState; - P: TPoint): Boolean; - -begin - Result := True; - if Assigned(FOnNodeHeightDblClickResize) then - FOnNodeHeightDblClickResize(Self, Node, Column, Shift, P, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoNodeHeightTracking(Node: PVirtualNode; Column: TColumnIndex; Shift: TShiftState; - var TrackPoint: TPoint; P: TPoint): Boolean; - -begin - Result := True; - if Assigned(FOnNodeHeightTracking) then - FOnNodeHeightTracking(Self, Node, Column, Shift, TrackPoint, P, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoNodeMoved(Node: PVirtualNode); - -begin - if Assigned(FOnNodeMoved) then - FOnNodeMoved(Self, Node); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoNodeMoving(Node, NewParent: PVirtualNode): Boolean; - -begin - Result := True; - if Assigned(FOnNodeMoving) then - FOnNodeMoving(Self, Node, NewParent, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoPaintBackground(Canvas: TCanvas; R: TRect): Boolean; - -begin - Result := False; - if Assigned(FOnPaintBackground) then - FOnPaintBackground(Self, Canvas, R, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoPaintDropMark(Canvas: TCanvas; Node: PVirtualNode; R: TRect); - -// draws the drop mark into the given rectangle -// Note: Changed properties of the given canvas should be reset to their previous values. - -var - SaveBrushColor: TColor; - SavePenStyle: TPenStyle; - -begin - if FLastDropMode in [dmAbove, dmBelow] then - with Canvas do - begin - SavePenStyle := Pen.Style; - Pen.Style := psClear; - SaveBrushColor := Brush.Color; - Brush.Color := FColors.DropMarkColor; - - if FLastDropMode = dmAbove then - begin - Polygon([Point(R.Left + 2, R.Top), - Point(R.Right - 2, R.Top), - Point(R.Right - 2, R.Top + 6), - Point(R.Right - 6, R.Top + 2), - Point(R.Left + 6 , R.Top + 2), - Point(R.Left + 2, R.Top + 6) - ]); - end - else - Polygon([Point(R.Left + 2, R.Bottom - 1), - Point(R.Right - 2, R.Bottom - 1), - Point(R.Right - 2, R.Bottom - 8), - Point(R.Right - 7, R.Bottom - 3), - Point(R.Left + 7 , R.Bottom - 3), - Point(R.Left + 2, R.Bottom - 8) - ]); - Brush.Color := SaveBrushColor; - Pen.Style := SavePenStyle; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoPaintNode(var PaintInfo: TVTPaintInfo); - -begin -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Position: TPoint); - -// Support for node dependent popup menus. - -var - Menu: TPopupMenu; - -begin - Menu := DoGetPopupMenu(Node, Column, Position); - - if Assigned(Menu) then - begin - DoStateChange([tsPopupMenuShown]); - StopTimer(EditTimer); - Menu.PopupComponent := Self; - with ClientToScreen(Position) do - Menu.Popup(X, Y); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoRemoveFromSelection(Node: PVirtualNode); - -begin - if Assigned(FOnRemoveFromSelection) then - FOnRemoveFromSelection(Self, Node); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoRenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; - ForClipboard: Boolean): HRESULT; - -begin - Result := E_FAIL; - if Assigned(FOnRenderOLEData) then - FOnRenderOLEData(Self, FormatEtcIn, Medium, ForClipboard, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoReset(Node: PVirtualNode); - -begin - if Assigned(FOnResetNode) then - FOnResetNode(Self, Node); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoSaveUserData(Node: PVirtualNode; Stream: TStream); - -begin - if Assigned(FOnSaveNode) then - if Node = FRoot then - FOnSaveNode(Self, nil, Stream) - else - FOnSaveNode(Self, Node, Stream); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoScroll(DeltaX, DeltaY: Integer); - -begin - if Assigned(FOnScroll) then - FOnScroll(Self, DeltaX, DeltaY); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoSetOffsetXY(Value: TPoint; Options: TScrollUpdateOptions; ClipRect: PRect = nil): Boolean; - -// Actual offset setter used to scroll the client area, update scroll bars and invalidating the header (all optional). -// Returns True if the offset really changed otherwise False is returned. - -var - DeltaX: Integer; - DeltaY: Integer; - DWPStructure: HDWP; - I: Integer; - P: TPoint; - R: TRect; - -begin - // Range check, order is important here. - if Value.X < (ClientWidth - Integer(FRangeX)) then - Value.X := ClientWidth - Integer(FRangeX); - if Value.X > 0 then - Value.X := 0; - DeltaX := Value.X - FOffsetX; - if UseRightToLeftAlignment then - DeltaX := -DeltaX; - if Value.Y < (ClientHeight - Integer(FRangeY)) then - Value.Y := ClientHeight - Integer(FRangeY); - if Value.Y > 0 then - Value.Y := 0; - DeltaY := Value.Y - FOffsetY; - - Result := (DeltaX <> 0) or (DeltaY <> 0); - if Result then - begin - FOffsetX := Value.X; - FOffsetY := Value.Y; - Result := True; - - if tsHint in Self.FStates then - Application.CancelHint; - if FUpdateCount = 0 then - begin - // The drag image from VCL controls need special consideration. - if tsVCLDragging in FStates then - ImageList_DragShowNolock(False); - - if (suoScrollClientArea in Options) and not (tsToggling in FStates) then - begin - // Have to invalidate the entire window if there's a background. - if (toShowBackground in FOptions.PaintOptions) and Assigned(FBackground.Graphic) then - begin - // Since we don't use ScrollWindow here we have to move all client windows ourselves. - DWPStructure := BeginDeferWindowPos(ControlCount); - for I := 0 to ControlCount - 1 do - if Controls[I] is TWinControl then - begin - with Controls[I] as TWinControl do - DWPStructure := DeferWindowPos(DWPStructure, Handle, 0, Left + DeltaX, Top + DeltaY, 0, 0, - SWP_NOZORDER or SWP_NOACTIVATE or SWP_NOSIZE); - if DWPStructure = 0 then - Break; - end; - if DWPStructure <> 0 then - EndDeferWindowPos(DWPStructure); - InvalidateRect(Handle, nil, False); - end - else - begin - if (DeltaX <> 0) and (Header.Columns.GetVisibleFixedWidth > 0) then - begin - // When fixed columns exists we have to scroll separately horizontally and vertically. - // Horizontally is scroll only the client area not occupied by fixed columns and - // vertically entire client area (or clipping area if one exists). - R := ClientRect; - R.Left := Header.Columns.GetVisibleFixedWidth; - - ScrollWindow(Handle, DeltaX, 0, @R, @R); - if DeltaY <> 0 then - ScrollWindow(Handle, 0, DeltaY, ClipRect, ClipRect); - end - else - ScrollWindow(Handle, DeltaX, DeltaY, ClipRect, ClipRect); - end; - end; - - if suoUpdateNCArea in Options then - begin - if DeltaX <> 0 then - begin - UpdateHorizontalScrollBar(suoRepaintScrollBars in Options); - if (suoRepaintHeader in Options) and (hoVisible in FHeader.Options) then - FHeader.Invalidate(nil); - if not (tsSizing in FStates) and (FScrollBarOptions.ScrollBars in [System.UITypes.TScrollStyle.ssHorizontal, System.UITypes.TScrollStyle.ssBoth]) then - UpdateVerticalScrollBar(suoRepaintScrollBars in Options); - end; - - if (DeltaY <> 0) and ([tsThumbTracking, tsSizing] * FStates = []) then - begin - UpdateVerticalScrollBar(suoRepaintScrollBars in Options); - if not (FHeader.UseColumns or IsMouseSelecting) and - (FScrollBarOptions.ScrollBars in [System.UITypes.TScrollStyle.ssHorizontal, System.UITypes.TScrollStyle.ssBoth]) then - UpdateHorizontalScrollBar(suoRepaintScrollBars in Options); - end; - end; - - if tsVCLDragging in FStates then - ImageList_DragShowNolock(True); - end; - - // Finally update "hot" node if hot tracking is activated - GetCursorPos(P); - P := ScreenToClient(P); - if PtInRect(ClientRect, P) then - HandleHotTrack(P.X, P.Y); - - DoScroll(DeltaX, DeltaY); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoShowScrollBar(Bar: Integer; Show: Boolean); - -begin - ShowScrollBar(Handle, Bar, Show); - if Assigned(FOnShowScrollBar) then - FOnShowScrollBar(Self, Bar, Show); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoStartDrag(var DragObject: TDragObject); - -begin - inherited; - - // Check if the application created an own drag object. This is needed to pass the correct source in - // OnDragOver and OnDragDrop. - if Assigned(DragObject) then - DoStateChange([tsUserDragObject]); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoStartOperation(OperationKind: TVTOperationKind); - -begin - if Assigned(FOnStartOperation) then - FOnStartOperation(Self, OperationKind); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoStateChange(Enter: TVirtualTreeStates; Leave: TVirtualTreeStates = []); - -var - ActualEnter, - ActualLeave: TVirtualTreeStates; - -begin - if Assigned(FOnStateChange) then - begin - ActualEnter := Enter - FStates; - ActualLeave := FStates * Leave; - if (ActualEnter + ActualLeave) <> [] then - FOnStateChange(Self, Enter, Leave); - end; - FStates := FStates + Enter - Leave; - Assert(FStates * [tsUseCache, tsValidationNeeded] <> [tsUseCache, tsValidationNeeded], 'Invalid state. tsUseCache and tsValidationNeeded are mutually exclusive and must not be set at the same time'); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoStructureChange(Node: PVirtualNode; Reason: TChangeReason); - -begin - StopTimer(StructureChangeTimer); - if Assigned(FOnStructureChange) then - FOnStructureChange(Self, Node, Reason); - - // This is a good place to reset the cached node and reason. These are the same as the values passed in here. - // This is necessary to allow descendants to override this method and get them. - DoStateChange([], [tsStructureChangePending]); - FLastStructureChangeNode := nil; - FLastStructureChangeReason := crIgnore; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoTimerScroll; - -var - P, - ClientP: TPoint; - InRect, - Panning: Boolean; - R, - ClipRect: TRect; - DeltaX, - DeltaY: Integer; - -begin - GetCursorPos(P); - R := ClientRect; - ClipRect := R; - MapWindowPoints(Handle, 0, R, 2); - InRect := PtInRect(R, P); - ClientP := ScreenToClient(P); - Panning := [tsWheelPanning, tsWheelScrolling] * FStates <> []; - - if IsMouseSelecting or InRect or Panning then - begin - DeltaX := 0; - DeltaY := 0; - if sdUp in FScrollDirections then - begin - if Panning then - DeltaY := FLastClickPos.Y - ClientP.Y - 8 - else - if InRect then - DeltaY := Min(FScrollBarOptions.VerticalIncrement, ClientHeight) - else - DeltaY := Min(FScrollBarOptions.VerticalIncrement, ClientHeight) * Abs(R.Top - P.Y); - if FOffsetY = 0 then - Exclude(FScrollDirections, sdUp); - end; - - if sdDown in FScrollDirections then - begin - if Panning then - DeltaY := FLastClickPos.Y - ClientP.Y + 8 - else - if InRect then - DeltaY := -Min(FScrollBarOptions.VerticalIncrement, ClientHeight) - else - DeltaY := -Min(FScrollBarOptions.VerticalIncrement, ClientHeight) * Abs(P.Y - R.Bottom); - if (ClientHeight - FOffsetY) = Integer(FRangeY) then - Exclude(FScrollDirections, sdDown); - end; - - if sdLeft in FScrollDirections then - begin - if Panning then - DeltaX := FLastClickPos.X - ClientP.X - 8 - else - if InRect then - DeltaX := FScrollBarOptions.HorizontalIncrement - else - DeltaX := FScrollBarOptions.HorizontalIncrement * Abs(R.Left - P.X); - if FEffectiveOffsetX = 0 then - Exclude(FScrollDirections, sdleft); - end; - - if sdRight in FScrollDirections then - begin - if Panning then - DeltaX := FLastClickPos.X - ClientP.X + 8 - else - if InRect then - DeltaX := -FScrollBarOptions.HorizontalIncrement - else - DeltaX := -FScrollBarOptions.HorizontalIncrement * Abs(P.X - R.Right); - - if (ClientWidth + FEffectiveOffsetX) = Integer(FRangeX) then - Exclude(FScrollDirections, sdRight); - end; - - if UseRightToLeftAlignment then - DeltaX := - DeltaX; - - if IsMouseSelecting then - begin - // In order to avoid scrolling the area which needs a repaint due to the changed selection rectangle - // we limit the scroll area explicitely. - OffsetRect(ClipRect, DeltaX, DeltaY); - DoSetOffsetXY(Point(FOffsetX + DeltaX, FOffsetY + DeltaY), DefaultScrollUpdateFlags, @ClipRect); - // When selecting with the mouse then either update only the parts of the window which have been uncovered - // by the scroll operation if no change in the selection happend or invalidate and redraw the entire - // client area otherwise (to avoid the time consuming task of determining the display rectangles of every - // changed node). - if CalculateSelectionRect(ClientP.X, ClientP.Y) and HandleDrawSelection(ClientP.X, ClientP.Y) then - InvalidateRect(Handle, nil, False) - else - begin - // The selection did not change so invalidate only the part of the window which really needs an update. - // 1) Invalidate the parts uncovered by the scroll operation. Add another offset range, we have to - // scroll only one stripe but have to update two. - OffsetRect(ClipRect, DeltaX, DeltaY); - SubtractRect(ClipRect, ClientRect, ClipRect); - InvalidateRect(Handle, @ClipRect, False); - - // 2) Invalidate the selection rectangles. - UnionRect(ClipRect, OrderRect(FNewSelRect), OrderRect(FLastSelRect)); - OffsetRect(ClipRect, FOffsetX, FOffsetY); - InvalidateRect(Handle, @ClipRect, False); - end; - end - else - begin - // Scroll only if there is no drag'n drop in progress. Drag'n drop scrolling is handled in DragOver. - if ((FDragManager = nil) or not DragManager.IsDropTarget) and ((DeltaX <> 0) or (DeltaY <> 0)) then - DoSetOffsetXY(Point(FOffsetX + DeltaX, FOffsetY + DeltaY), DefaultScrollUpdateFlags, nil); - end; - UpdateWindow(Handle); - - if (FScrollDirections = []) and ([tsWheelPanning, tsWheelScrolling] * FStates = []) then - begin - StopTimer(ScrollTimer); - DoStateChange([], [tsScrollPending, tsScrolling]); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoUpdating(State: TVTUpdateState); - -begin - if Assigned(FOnUpdating) then - FOnUpdating(Self, State); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DoValidateCache(): Boolean; - -// This method fills the cache, which is used to speed up searching for nodes. -// The strategy is simple: Take the current number of visible nodes and distribute evenly a number of marks -// (which are stored in FPositionCache) so that iterating through the tree doesn't cost too much time. -// If there are less than 'CacheThreshold' nodes in the tree then the cache remains empty. -// Result is True if the cache was filled without interruption, otherwise False. -// Note: You can adjust the maximum number of nodes between two cache entries by changing CacheThreshold. - -var - EntryCount, - CurrentTop, - Index: Cardinal; - CurrentNode, - Temp: PVirtualNode; - -begin - EntryCount := 0; - if not (tsStopValidation in FStates) then - begin - if FStartIndex = 0 then - FPositionCache := nil; - - EntryCount := CalculateCacheEntryCount; - SetLength(FPositionCache, EntryCount); - if FStartIndex > EntryCount then - FStartIndex := EntryCount; - - // Optimize validation by starting with FStartIndex if set. - if (FStartIndex > 0) and Assigned(FPositionCache[FStartIndex - 1].Node) then - begin - // Index is the current entry in FPositionCache. - Index := FStartIndex - 1; - // Running term for absolute top value. - CurrentTop := FPositionCache[Index].AbsoluteTop; - // Running node pointer. - CurrentNode := FPositionCache[Index].Node; - end - else - begin - // Index is the current entry in FPositionCache. - Index := 0; - // Running term for absolute top value. - CurrentTop := 0; - // Running node pointer. - CurrentNode := GetFirstVisibleNoInit(nil, True); - end; - - // EntryCount serves as counter for processed nodes here. This value can always start at 0 as - // the validation either starts also at index 0 or an index which is always a multiple of CacheThreshold - // and EntryCount is only used with modulo CacheThreshold. - EntryCount := 0; - if Assigned(CurrentNode) then - begin - while not (tsStopValidation in FStates) do - begin - // If the cache is full then stop the loop. - if (Integer(Index) >= Length(FPositionCache)) then - Break; - if (EntryCount mod CacheThreshold) = 0 then - begin - // New cache entry to set up. - with FPositionCache[Index] do - begin - Node := CurrentNode; // 2 EAccessViolation seen here in TreeSize V4.3.1, 1 in V4.4.0 (Write of address 00000000) - AbsoluteTop := CurrentTop; - end; - Inc(Index); - end; - - Inc(CurrentTop, NodeHeight[CurrentNode]); - // Advance to next visible node. - Temp := GetNextVisibleNoInit(CurrentNode, True); - // If there is no further node then stop the loop. - if (Temp = nil) then // CHANGED: 17.09.2013 - Veit Zimmermann - Break; // CHANGED: 17.09.2013 - Veit Zimmermann - - CurrentNode := Temp; - Inc(EntryCount); - end; - end; - // Finalize the position cache so no nil entry remains there. - if not (tsStopValidation in FStates) and (Integer(Index) <= High(FPositionCache)) then - begin - SetLength(FPositionCache, Index + 1); - with FPositionCache[Index] do - begin - Node := CurrentNode; - AbsoluteTop := CurrentTop; - end; - end; - end; - - Result := (EntryCount > 0) and not (tsStopValidation in FStates); - - // In variable node height mode it might have happend that some or all of the nodes have been adjusted in their - // height. During validation updates of the scrollbars is disabled so let's do this here. - if Result and (toVariableNodeHeight in FOptions.MiscOptions) then - begin - TThread.Queue(nil, procedure begin UpdateScrollBars(True) end); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DragAndDrop(AllowedEffects: Dword; const DataObject: IDataObject; var DragEffect: Integer); -var - lDragEffect: DWord; // required for type compatibility with SHDoDragDrop -begin - if IsWinVistaOrAbove then - begin - lDragEffect := DWord(DragEffect); - SHDoDragDrop(Self.Handle, DataObject, nil, AllowedEffects, lDragEffect); // supports drag hints on Windows Vista and later - DragEffect := Integer(lDragEffect); - end - else - Winapi.ActiveX.DoDragDrop(DataObject, DragManager as IDropSource, AllowedEffects, DragEffect); - end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DragCanceled; - -// Does some housekeeping for VCL drag'n drop; - -begin - inherited; - - DragFinished; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DragDrop(const DataObject: IDataObject; KeyState: Integer; Pt: TPoint; - var Effect: Integer): HResult; - -var - Shift: TShiftState; - EnumFormat: IEnumFormatEtc; - Fetched: Integer; - OLEFormat: TFormatEtc; - Formats: TFormatArray; - -begin - StopTimer(ExpandTimer); - StopTimer(ScrollTimer); - DoStateChange([], [tsScrollPending, tsScrolling]); - Formats := nil; - - // Ask explicitly again whether the action is allowed. Otherwise we may accept a drop which is intentionally not - // allowed but cannot be prevented by the application because when the tree was scrolling while dropping - // no DragOver event is created by the OLE subsystem. - Result := DragOver(DragManager.DragSource, KeyState, dsDragMove, Pt, Effect); - try - if (Result <> NOERROR) or ((Effect and not DROPEFFECT_SCROLL) = DROPEFFECT_NONE) then - Result := E_FAIL - else - begin - try - Shift := KeysToShiftState(KeyState); - if tsRightButtonDown in FStates then - Include(Shift, ssRight) - else if tsMiddleButtonDown in FStates then - Include(Shift, ssMiddle) - else - Include(Shift, ssLeft); - Pt := ScreenToClient(Pt); - // Determine which formats we can get and pass them along with the data object to the drop handler. - Result := DataObject.EnumFormatEtc(DATADIR_GET, EnumFormat); - if Failed(Result) then - Abort; - Result := EnumFormat.Reset; - if Failed(Result) then - Abort; - // create a list of available formats - while EnumFormat.Next(1, OLEFormat, @Fetched) = S_OK do - begin - SetLength(Formats, Length(Formats) + 1); - Formats[High(Formats)] := OLEFormat.cfFormat; - end; - DoDragDrop(DragManager.DragSource, DataObject, Formats, Shift, Pt, Effect, FLastDropMode); - except - // An unhandled exception here leaks memory. - Application.HandleException(Self); - Result := E_UNEXPECTED; - end; - end; - finally - if Assigned(FDropTargetNode) then - begin - InvalidateNode(FDropTargetNode); - FDropTargetNode := nil; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DragEnter(KeyState: Integer; Pt: TPoint; var Effect: Integer): HResult; - -// callback routine for the drop target interface - -var - Shift: TShiftState; - Accept: Boolean; - R: TRect; - HitInfo: THitInfo; - -begin - try - // Determine acceptance of drag operation and reset scroll start time. - FDragScrollStart := 0; - - Shift := KeysToShiftState(KeyState); - if tsLeftButtonDown in FStates then - Include(Shift, ssLeft); - if tsMiddleButtonDown in FStates then - Include(Shift, ssMiddle); - if tsRightButtonDown in FStates then - Include(Shift, ssRight); - Pt := ScreenToClient(Pt); - Effect := SuggestDropEffect(DragManager.DragSource, Shift, Pt, Effect); - Accept := DoDragOver(DragManager.DragSource, Shift, dsDragEnter, Pt, FLastDropMode, Effect); - if not Accept then - Effect := DROPEFFECT_NONE - else - begin - // Set initial drop target node and drop mode. - GetHitTestInfoAt(Pt.X, Pt.Y, True, HitInfo); - if Assigned(HitInfo.HitNode) then - begin - FDropTargetNode := HitInfo.HitNode; - R := GetDisplayRect(HitInfo.HitNode, FHeader.MainColumn, False); - if (hiOnItemLabel in HitInfo.HitPositions) or ((hiOnItem in HitInfo.HitPositions) and - ((toFullRowDrag in FOptions.MiscOptions) or (toFullRowSelect in FOptions.SelectionOptions)))then - FLastDropMode := dmOnNode - else - if ((R.Top + R.Bottom) div 2) > Pt.Y then - FLastDropMode := dmAbove - else - FLastDropMode := dmBelow; - end - else - FLastDropMode := dmNowhere; - end; - - // If the drag source is a virtual tree then we know how to control the drag image - // and can show it even if the source is not the target tree. - // This is only necessary if we cannot use the drag image helper interfaces. - if not DragManager.DropTargetHelperSupported and Assigned(DragManager.DragSource) then - DragManager.DragSource.FDragImage.ShowDragImage; - Result := NOERROR; - except - Result := E_UNEXPECTED; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DragFinished; - -// Called by DragCancelled or EndDrag to make up for the still missing mouse button up messages. -// These are important for such important things like popup menus. - -var - P: TPoint; - -begin - if [tsOLEDragging, tsVCLDragPending, tsVCLDragging, tsVCLDragFinished] * FStates = [] then - Exit; - - DoStateChange([], [tsVCLDragPending, tsVCLDragging, tsUserDragObject, tsVCLDragFinished]); - - GetCursorPos(P); - P := ScreenToClient(P); - if tsRightButtonDown in FStates then - Perform(WM_RBUTTONUP, 0, LPARAM(Integer(PointToSmallPoint(P)))) - else - if tsMiddleButtonDown in FStates then - Perform(WM_MBUTTONUP, 0, LPARAM(Integer(PointToSmallPoint(P)))) - else - Perform(WM_LBUTTONUP, 0, LPARAM(Integer(PointToSmallPoint(P)))); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DragLeave; - -var - Effect: Integer; - -begin - StopTimer(ExpandTimer); - - if not DragManager.DropTargetHelperSupported and Assigned(DragManager.DragSource) then - DragManager.DragSource.FDragImage.HideDragImage; - - if Assigned(FDropTargetNode) then - begin - InvalidateNode(FDropTargetNode); - FDropTargetNode := nil; - end; - UpdateWindow(Handle); - - Effect := 0; - DoDragOver(nil, [], dsDragLeave, Point(0, 0), FLastDropMode, Effect); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.DragOver(Source: TObject; KeyState: Integer; DragState: TDragState; Pt: TPoint; - var Effect: Integer): HResult; - -// callback routine for the drop target interface - -var - Shift: TShiftState; - Accept, - DragImageWillMove, - WindowScrolled: Boolean; - OldR, R: TRect; - NewDropMode: TDropMode; - HitInfo: THitInfo; - DragPos: TPoint; - Tree: TBaseVirtualTree; - LastNode: PVirtualNode; - DeltaX, - DeltaY: Integer; - ScrollOptions: TScrollUpdateOptions; - -begin - if not DragManager.DropTargetHelperSupported and (Source is TBaseVirtualTree) then - begin - Tree := Source as TBaseVirtualTree; - ScrollOptions := [suoUpdateNCArea]; - end - else - begin - Tree := nil; - ScrollOptions := DefaultScrollUpdateFlags; - end; - - try - DragPos := Pt; - Pt := ScreenToClient(Pt); - - // Check if we have to scroll the client area. - FScrollDirections := DetermineScrollDirections(Pt.X, Pt.Y); - DeltaX := 0; - DeltaY := 0; - if FScrollDirections <> [] then - begin - // Determine amount to scroll. - if sdUp in FScrollDirections then - begin - DeltaY := Min(FScrollBarOptions.VerticalIncrement, ClientHeight); - if FOffsetY = 0 then - Exclude(FScrollDirections, sdUp); - end; - if sdDown in FScrollDirections then - begin - DeltaY := -Min(FScrollBarOptions.VerticalIncrement, ClientHeight); - if (ClientHeight - FOffsetY) = Integer(FRangeY) then - Exclude(FScrollDirections, sdDown); - end; - if sdLeft in FScrollDirections then - begin - DeltaX := FScrollBarOptions.HorizontalIncrement; - if FEffectiveOffsetX = 0 then - Exclude(FScrollDirections, sdleft); - end; - if sdRight in FScrollDirections then - begin - DeltaX := -FScrollBarOptions.HorizontalIncrement; - if (ClientWidth + FEffectiveOffsetX) = Integer(FRangeX) then - Exclude(FScrollDirections, sdRight); - end; - WindowScrolled := DoSetOffsetXY(Point(FOffsetX + DeltaX, FOffsetY + DeltaY), ScrollOptions, nil); - end - else - WindowScrolled := False; - - // Determine acceptance of drag operation as well as drag target. - Shift := KeysToShiftState(KeyState); - if tsLeftButtonDown in FStates then - Include(Shift, ssLeft); - if tsMiddleButtonDown in FStates then - Include(Shift, ssMiddle); - if tsRightButtonDown in FStates then - Include(Shift, ssRight); - GetHitTestInfoAt(Pt.X, Pt.Y, True, HitInfo); - - if Assigned(HitInfo.HitNode) then - R := GetDisplayRect(HitInfo.HitNode, NoColumn, False) - else - R := Rect(0, 0, 0, 0); - NewDropMode := DetermineDropMode(Pt, HitInfo, R); - - if Assigned(Tree) then - DragImageWillMove := Tree.FDragImage.WillMove(DragPos) - else - DragImageWillMove := False; - - if (HitInfo.HitNode <> FDropTargetNode) or (FLastDropMode <> NewDropMode) then - begin - // Something in the tree will change. This requires to update the screen and/or the drag image. - FLastDropMode := NewDropMode; - if HitInfo.HitNode <> FDropTargetNode then - begin - StopTimer(ExpandTimer); - // The last target node is needed for the rectangle determination but must already be set for - // the recapture call, hence it must be stored somewhere. - LastNode := FDropTargetNode; - FDropTargetNode := HitInfo.HitNode; - // In order to show a selection rectangle a column must be focused. - if FFocusedColumn <= NoColumn then - FFocusedColumn := FHeader.MainColumn; - - if Assigned(LastNode) and Assigned(FDropTargetNode) then - begin - // Optimize the case that the selection moved between two nodes. - OldR := GetDisplayRect(LastNode, NoColumn, False); - UnionRect(R, R, OldR); - if Assigned(Tree) then - begin - if WindowScrolled then - UpdateWindowAndDragImage(Tree, ClientRect, True, not DragImageWillMove) - else - UpdateWindowAndDragImage(Tree, R, False, not DragImageWillMove); - end - else - InvalidateRect(Handle, @R, False); - end - else - begin - if Assigned(LastNode) then - begin - // Repaint last target node. - OldR := GetDisplayRect(LastNode, NoColumn, False); - if Assigned(Tree) then - begin - if WindowScrolled then - UpdateWindowAndDragImage(Tree, ClientRect, WindowScrolled, not DragImageWillMove) - else - UpdateWindowAndDragImage(Tree, OldR, False, not DragImageWillMove); - end - else - InvalidateRect(Handle, @OldR, False); - end - else - begin - if Assigned(Tree) then - begin - if WindowScrolled then - UpdateWindowAndDragImage(Tree, ClientRect, WindowScrolled, not DragImageWillMove) - else - UpdateWindowAndDragImage(Tree, R, False, not DragImageWillMove); - end - else - InvalidateRect(Handle, @R, False); - end; - end; - - // Start auto expand timer if necessary. - if (toAutoDropExpand in FOptions.AutoOptions) and Assigned(FDropTargetNode) and - (vsHasChildren in FDropTargetNode.States) then - SetTimer(Handle, ExpandTimer, FAutoExpandDelay, nil); - end - else - begin - // Only the drop mark position changed so invalidate the current drop target node. - if Assigned(Tree) then - begin - if WindowScrolled then - UpdateWindowAndDragImage(Tree, ClientRect, WindowScrolled, not DragImageWillMove) - else - UpdateWindowAndDragImage(Tree, R, False, not DragImageWillMove); - end - else - InvalidateRect(Handle, @R, False); - end; - end - else - begin - // No change in the current drop target or drop mode. This might still mean horizontal or vertical scrolling. - if Assigned(Tree) and ((DeltaX <> 0) or (DeltaY <> 0)) then - UpdateWindowAndDragImage(Tree, ClientRect, WindowScrolled, not DragImageWillMove); - end; - - Update; - - if Assigned(Tree) and DragImageWillMove then - Tree.FDragImage.DragTo(DragPos, False); - - Effect := SuggestDropEffect(Source, Shift, Pt, Effect); - Accept := DoDragOver(Source, Shift, DragState, Pt, FLastDropMode, Effect); - if not Accept then - Effect := DROPEFFECT_NONE; - if WindowScrolled then - Effect := Effect or Integer(DROPEFFECT_SCROLL); - Result := NOERROR; - except - Result := E_UNEXPECTED; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DrawDottedHLine(const PaintInfo: TVTPaintInfo; Left, Right, Top: Integer); - -// Draws a horizontal line with alternating pixels (this style is not supported for pens under Win9x). - -var - R: TRect; - -begin - with PaintInfo, Canvas do - begin - Brush.Color := FColors.BackGroundColor; - R := Rect(Min(Left, Right), Top, Max(Left, Right) + 1, Top + 1); - Winapi.Windows.FillRect(Handle, R, FDottedBrush - ); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DrawDottedVLine(const PaintInfo: TVTPaintInfo; Top, Bottom, Left: Integer; UseSelectedBkColor: Boolean = False); - -// Draws a horizontal line with alternating pixels (this style is not supported for pens under Win9x). - -var - R: TRect; - -begin - with PaintInfo, Canvas do - begin - if UseSelectedBkColor then - begin - if Focused or (toPopupMode in FOptions.PaintOptions) then - Brush.Color := FColors.FocusedSelectionColor - else - Brush.Color := FColors.UnfocusedSelectionColor; - end - else - Brush.Color := FColors.BackGroundColor; - R := Rect(Left, Min(Top, Bottom), Left + 1, Max(Top, Bottom) + 1); - Winapi.Windows.FillRect(Handle, R, FDottedBrush); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.EndOperation(OperationKind: TVTOperationKind); - -// Called to indicate that a long-running operation has finished. - -begin - Assert(FOperationCount > 0, 'EndOperation must not be called when no operation in progress.'); - Dec(FOperationCount); - DoEndOperation(OperationKind); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.EnsureNodeFocused(); -begin - if FocusedNode = nil then - FocusedNode := Self.GetFirstSelected(); - if FocusedNode = nil then - FocusedNode := Self.GetFirstVisible(); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.EnsureNodeSelected(); -begin - if (toAlwaysSelectNode in TreeOptions.SelectionOptions) and not IsEmpty then - begin - if (SelectedCount = 0) and not SelectionLocked then - begin - if not Assigned(FNextNodeToSelect) then - begin - FNextNodeToSelect := GetFirstVisible; - // Avoid selecting a disabled node, see #954 - while Assigned(FNextNodeToSelect) and IsDisabled[FNextNodeToSelect] do - FNextNodeToSelect := GetNextVisible(FNextNodeToSelect); - end; - Selected[FNextNodeToSelect] := True; - Self.ScrollIntoView(Self.GetFirstSelected, False); - end;// if nothing selected - EnsureNodeFocused(); - end;//if toAlwaysSelectNode -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.FindNodeInSelection(P: PVirtualNode; var Index: Integer; LowBound, - HighBound: Integer): Boolean; - -// Search routine to find a specific node in the selection array. -// LowBound and HighBound determine the range in which to search the node. -// Either value can be -1 to denote the maximum range otherwise LowBound must be less or equal HighBound. - -var - L, H, - I: Integer; - -begin - Result := False; - L := 0; - if LowBound >= 0 then - L := LowBound; - H := FSelectionCount - 1; - if HighBound >= 0 then - H := HighBound; - while L <= H do - begin - I := (L + H) shr 1; - if PAnsiChar(FSelection[I]) < PAnsiChar(P) then - L := I + 1 - else - begin - H := I - 1; - if FSelection[I] = P then - begin - Result := True; - L := I; - end; - end; - end; - Index := L; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.FinishChunkHeader(Stream: TStream; StartPos, EndPos: Integer); - -// used while streaming out a node to finally write out the size of the chunk - -var - Size: Integer; - -begin - // seek back to the second entry in the chunk header - Stream.Position := StartPos + SizeOf(Size); - // determine size of chunk without the chunk header - Size := EndPos - StartPos - SizeOf(TChunkHeader); - // write the size... - Stream.Write(Size, SizeOf(Size)); - // ... and seek to the last endposition - Stream.Position := EndPos; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.FontChanged(AFont: TObject); - -// Little helper function for font changes (as they are not tracked in TBitmap/TCanvas.OnChange). - -begin - FFontChanged := True; - if Assigned(FOldFontChange) then - FOldFontChange(AFont); - //if not (tsPainting in TreeStates) then AutoScale(); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetBorderDimensions: TSize; - -// Returns the overall width of the current window border, depending on border styles. -// Note: these numbers represent the system's standards not special properties, which can be set for TWinControl -// (e.g. bevels, border width). - -var - Styles: Integer; - -begin - Result.cx := 0; - Result.cy := 0; - - Styles := GetWindowLong(Handle, GWL_STYLE); - if (Styles and WS_BORDER) <> 0 then - begin - Dec(Result.cx); - Dec(Result.cy); - end; - if (Styles and WS_THICKFRAME) <> 0 then - begin - Dec(Result.cx, GetSystemMetrics(SM_CXFIXEDFRAME)); - Dec(Result.cy, GetSystemMetrics(SM_CYFIXEDFRAME)); - end; - Styles := GetWindowLong(Handle, GWL_EXSTYLE); - if (Styles and WS_EX_CLIENTEDGE) <> 0 then - begin - Dec(Result.cx, GetSystemMetrics(SM_CXEDGE)); - Dec(Result.cy, GetSystemMetrics(SM_CYEDGE)); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetCheckImage(Node: PVirtualNode; ImgCheckType: TCheckType = ctNone; ImgCheckState: - TCheckState = csUncheckedNormal; ImgEnabled: Boolean = True): Integer; - -// Determines the index into the check image list for the given node depending on the check type -// and enabled state. - -const - // Four dimensional array consisting of image indices for the check type, the check state, the enabled state and the - // hot state. - CheckStateToCheckImage: array[ctCheckBox..ctButton, csUncheckedNormal..csMixedPressed, Boolean, Boolean] of Integer = ( - // ctCheckBox, ctTriStateCheckBox - ( - // csUncheckedNormal (disabled [not hot, hot], enabled [not hot, hot]) - ((ckCheckUncheckedDisabled, ckCheckUncheckedDisabled), (ckCheckUncheckedNormal, ckCheckUncheckedHot)), - // csUncheckedPressed (disabled [not hot, hot], enabled [not hot, hot]) - ((ckCheckUncheckedDisabled, ckCheckUncheckedDisabled), (ckCheckUncheckedPressed, ckCheckUncheckedPressed)), - // csCheckedNormal - ((ckCheckCheckedDisabled, ckCheckCheckedDisabled), (ckCheckCheckedNormal, ckCheckCheckedHot)), - // csCheckedPressed - ((ckCheckCheckedDisabled, ckCheckCheckedDisabled), (ckCheckCheckedPressed, ckCheckCheckedPressed)), - // csMixedNormal - ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedNormal, ckCheckMixedHot)), - // csMixedPressed - ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedPressed, ckCheckMixedPressed)) - ), - // ctRadioButton - ( - // csUncheckedNormal (disabled [not hot, hot], enabled [not hot, hot]) - ((ckRadioUncheckedDisabled, ckRadioUncheckedDisabled), (ckRadioUncheckedNormal, ckRadioUncheckedHot)), - // csUncheckedPressed (disabled [not hot, hot], enabled [not hot, hot]) - ((ckRadioUncheckedDisabled, ckRadioUncheckedDisabled), (ckRadioUncheckedPressed, ckRadioUncheckedPressed)), - // csCheckedNormal - ((ckRadioCheckedDisabled, ckRadioCheckedDisabled), (ckRadioCheckedNormal, ckRadioCheckedHot)), - // csCheckedPressed - ((ckRadioCheckedDisabled, ckRadioCheckedDisabled), (ckRadioCheckedPressed, ckRadioCheckedPressed)), - // csMixedNormal (should never appear with ctRadioButton) - ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedNormal, ckCheckMixedHot)), - // csMixedPressed (should never appear with ctRadioButton) - ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedPressed, ckCheckMixedPressed)) - ), - // ctButton - ( - // csUncheckedNormal (disabled [not hot, hot], enabled [not hot, hot]) - ((ckButtonDisabled, ckButtonDisabled), (ckButtonNormal, ckButtonHot)), - // csUncheckedPressed (disabled [not hot, hot], enabled [not hot, hot]) - ((ckButtonDisabled, ckButtonDisabled), (ckButtonPressed, ckButtonPressed)), - // csCheckedNormal - ((ckButtonDisabled, ckButtonDisabled), (ckButtonNormal, ckButtonHot)), - // csCheckedPressed - ((ckButtonDisabled, ckButtonDisabled), (ckButtonPressed, ckButtonPressed)), - // csMixedNormal (should never appear with ctButton) - ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedNormal, ckCheckMixedHot)), - // csMixedPressed (should never appear with ctButton) - ((ckCheckMixedDisabled, ckCheckMixedDisabled), (ckCheckMixedPressed, ckCheckMixedPressed)) - ) - ); - -var - IsHot: Boolean; - -begin - if Assigned(Node) then - begin - ImgCheckType := Node.CheckType; - ImgCheckState := GetCheckState(Node); - ImgEnabled := not (vsDisabled in Node.States) and Self.Enabled; - - IsHot := Node = FCurrentHotNode; - end - else - IsHot := False; - - if ImgCheckState.IsDisabled then begin // disabled image? - // We need to use disabled images, so map ImgCheckState value from disabled to normal, as disabled state is expressed by ImgEnabled. - ImgEnabled := False; - ImgCheckState := ImgCheckState.GetEnabled(); - end;//if - - if ImgCheckType = ctTriStateCheckBox then - ImgCheckType := ctCheckBox; - if IsHot and (ImgCheckState in [csCheckedNormal, csUncheckedNormal]) and (GetKeyState(VK_LBUTTON) < 0) and (hiOnItemCheckbox in FLastHitInfo.HitPositions) then - Inc(ImgCheckState); // Advance to pressed state - - if ImgCheckType = ctNone then - Result := -1 - else - Result := CheckStateToCheckImage[ImgCheckType, ImgCheckState, ImgEnabled, IsHot]; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetColumnClass: TVirtualTreeColumnClass; - -begin - Result := TVirtualTreeColumn; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetHeaderClass: TVTHeaderClass; - -begin - Result := TVTHeader; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetHintWindowClass: THintWindowClass; - -// Returns the default hint window class used for the tree. Descendants can override it to use their own classes. - -begin - Result := TVirtualTreeHintWindow; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.GetImageIndex(var Info: TVTPaintInfo; Kind: TVTImageKind; InfoIndex: TVTImageInfoIndex); - -// Retrieves the image index and an eventual customized image list for drawing. - -var - CustomImages: TCustomImageList; - -begin - with Info do - begin - ImageInfo[InfoIndex].Index := -1; - ImageInfo[InfoIndex].Ghosted := False; - - CustomImages := DoGetImageIndex(Node, Kind, Column, ImageInfo[InfoIndex].Ghosted, ImageInfo[InfoIndex].Index); - if Assigned(CustomImages) then - ImageInfo[InfoIndex].Images := CustomImages - end; -end; - -function TBaseVirtualTree.GetImageSize(Node: PVirtualNode; Kind: TVTImageKind = TVTImageKind.ikNormal; Column: TColumnIndex = 0; IncludePadding: Boolean = True): TSize; - -// Determines whether the given node has got an image of the given kind in the given column. -// Returns the size of the image, or (0,0) if no image is available -// The given node will be implicitly initialized if needed. - -var - Ghosted: Boolean; - Index: TImageIndex; - lImageList: TCustomImageList; -begin - if not Assigned(OnGetImageIndexEx) and (((Kind = TVTImageKind.ikNormal) and not Assigned(fImages)) - or ((Kind = TVTImageKind.ikState) and not Assigned(fStateImages))) then - begin - Result.cx := 0; - Result.cy := 0; - end; - if not (vsInitialized in Node.States) then - InitNode(Node); - Index := -1; - Ghosted := False; - lImageList := DoGetImageIndex(Node, Kind, Column, Ghosted, Index); - if Index >= 0 then begin - if IncludePadding then - Result.cx := lImageList.Width + ScaledPixels(2) - else - Result.cx := lImageList.Width; - Result.cy := lImageList.Height; - end - else begin - Result.cx := 0; - Result.cy := 0; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.IsEmpty: Boolean; -begin - Result := (Self.ChildCount[nil] = 0); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNodeImageSize(Node: PVirtualNode): TSize; - - // Returns the size of an image - // Override if you need different sized images for certain nodes. -begin - Result := GetImageSize(Node); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetMaxRightExtend(): Cardinal; - -// Determines the maximum with of the currently visible part of the tree, depending on the length -// of the node texts. This method is used for determining the horizontal scroll range if no columns are used. - -var - Node, - NextNode: PVirtualNode; - TopPosition: Integer; - CurrentWidth: Integer; - -begin - Node := GetNodeAt(0, 0, True, TopPosition); - Result := 0; - if not Assigned(Node) then - exit; - - while Assigned(Node) do - begin - if not (vsInitialized in Node.States) then - InitNode(Node); - CurrentWidth := GetOffset(TVTElement.ofsRightOfText, Node); - if Integer(Result) < (CurrentWidth) then - Result := CurrentWidth; - Inc(TopPosition, NodeHeight[Node]); - if TopPosition > Height then - Break; - - // Get next visible node and update left node position. - NextNode := GetNextVisible(Node, True); - if NextNode = nil then - Break; - Node := NextNode; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.GetNativeClipboardFormats(var Formats: TFormatEtcArray); - -// Returns the supported clipboard formats of the tree. - -begin - TClipboardFormatList.EnumerateFormats(TVirtualTreeClass(ClassType), Formats, FClipboardFormats); - // Ask application/descendants for self defined formats. - DoGetUserClipboardFormats(Formats); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetOperationCanceled; - -begin - Result := FOperationCanceled and (FOperationCount > 0); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetOptionsClass: TTreeOptionsClass; - -begin - Result := TCustomVirtualTreeOptions; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetTreeFromDataObject(const DataObject: IDataObject): TBaseVirtualTree; - -// Returns the owner/sender of the given data object by means of a special clipboard format -// or nil if the sender is in another process or no virtual tree at all. - -var - Medium: TStgMedium; - Data: PVTReference; - -begin - Result := nil; - if Assigned(DataObject) then - begin - StandardOLEFormat.cfFormat := CF_VTREFERENCE; - if DataObject.GetData(StandardOLEFormat, Medium) = S_OK then - begin - Data := GlobalLock(Medium.hGlobal); - if Assigned(Data) then - begin - if Data.Process = GetCurrentProcessID then - Result := Data.Tree; - GlobalUnlock(Medium.hGlobal); - end; - ReleaseStgMedium(Medium); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.HandleHotTrack(X, Y: Integer); - -// Updates the current "hot" node. - -var - HitInfo: THitInfo; - CheckPositions: THitPositions; - ButtonIsHit, - DoInvalidate: Boolean; - oldHotNode : PVirtualNode; -begin - if not IsMouseCursorVisible then - begin - if Assigned(FCurrentHotNode) then - begin - InvalidateNode(FCurrentHotNode); - FCurrentHotNode := nil; - end; - Exit; - end;//if not IsMouseCursorVisible - - DoInvalidate := False; - oldHotNode := FCurrentHotNode; - // Get information about the hit. - GetHitTestInfoAt(X, Y, True, HitInfo); - - // Only make the new node being "hot" if its label is hit or full row selection is enabled. - CheckPositions := [hiOnItemLabel, hiOnItemCheckbox]; - - // If running under Windows Vista using the explorer theme hitting the buttons makes the node hot, too. - if tsUseExplorerTheme in FStates then - Include(CheckPositions, hiOnItemButtonExact); - - if (CheckPositions * HitInfo.HitPositions = []) and - (not (toFullRowSelect in FOptions.SelectionOptions) or (hiNowhere in HitInfo.HitPositions)) then - FCurrentHotNode := nil - else - FCurrentHotNode := HitInfo.HitNode; - if (FCurrentHotNode <> oldHotNode) or (HitInfo.HitColumn <> FCurrentHotColumn) then - begin - DoInvalidate := (toHotTrack in FOptions.PaintOptions) or (toCheckSupport in FOptions.MiscOptions) or (oldHotNode <> FCurrentHotNode); - DoHotChange(oldHotNode, HitInfo.HitNode); - if Assigned(oldHotNode) and DoInvalidate then - InvalidateNode(oldHotNode); - FCurrentHotColumn := HitInfo.HitColumn; - end; - - ButtonIsHit := (hiOnItemButtonExact in HitInfo.HitPositions); - if Assigned(HitInfo.HitNode) and ((FHotNodeButtonHit <> ButtonIsHit) or (FCurrentHotNode <> oldHotNode) or DoInvalidate) then - begin - FHotNodeButtonHit := ButtonIsHit; - InvalidateNode(HitInfo.HitNode); - end - else - if not Assigned(HitInfo.HitNode) then - FHotNodeButtonHit := False; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.HandleIncrementalSearch(CharCode: Word); - -var - Run, Stop: PVirtualNode; - GetNextNode: TGetNextNodeProc; - NewSearchText: string; - SingleLetter, - PreviousSearch: Boolean; // True if VK_BACK was sent. - SearchDirection: TVTSearchDirection; - - //--------------- local functions ------------------------------------------- - - procedure SetupNavigation; - - // If the search buffer is empty then we start searching with the next node after the last one, otherwise - // we continue with the last one. Node navigation function is set up too here, to avoid frequent checks. - - var - FindNextNode: Boolean; - - begin - FindNextNode := (Length(FSearchBuffer) = 0) or (Run = nil) or SingleLetter or PreviousSearch; - case FIncrementalSearch of - isVisibleOnly: - if SearchDirection = sdForward then - begin - GetNextNode := GetNextVisible; - if FindNextNode then - begin - if Run = nil then - Run := GetFirstVisible(nil, True) - else - begin - Run := GetNextVisible(Run, True); - // Do wrap around. - if Run = nil then - Run := GetFirstVisible(nil, True); - end; - end; - end - else - begin - GetNextNode := GetPreviousVisible; - if FindNextNode then - begin - if Run = nil then - Run := GetLastVisible(nil, True) - else - begin - Run := GetPreviousVisible(Run, True); - // Do wrap around. - if Run = nil then - Run := GetLastVisible(nil, True); - end; - end; - end; - isInitializedOnly: - if SearchDirection = sdForward then - begin - GetNextNode := GetNextNoInit; - if FindNextNode then - begin - if Run = nil then - Run := GetFirstNoInit - else - begin - Run := GetNextNoInit(Run); - // Do wrap around. - if Run = nil then - Run := GetFirstNoInit; - end; - end; - end - else - begin - GetNextNode := GetPreviousNoInit; - if FindNextNode then - begin - if Run = nil then - Run := GetLastNoInit - else - begin - Run := GetPreviousNoInit(Run); - // Do wrap around. - if Run = nil then - Run := GetLastNoInit; - end; - end; - end; - else - // isAll - if SearchDirection = sdForward then - begin - GetNextNode := GetNext; - if FindNextNode then - begin - if Run = nil then - Run := GetFirst - else - begin - Run := GetNext(Run); - // Do wrap around. - if Run = nil then - Run := GetFirst; - end; - end; - end - else - begin - GetNextNode := GetPrevious; - if FindNextNode then - begin - if Run = nil then - Run := GetLast - else - begin - Run := GetPrevious(Run); - // Do wrap around. - if Run = nil then - Run := GetLast; - end; - end; - end; - end; - end; - - //--------------------------------------------------------------------------- - - function CodePageFromLocale(Language: LCID): Integer; - - // Determines the code page for a given locale. - // Unfortunately there is no easier way than this, currently. - - var - Buf: array[0..6] of Char; - - begin - GetLocaleInfo(Language, LOCALE_IDEFAULTANSICODEPAGE, Buf, 6); - Result := StrToIntDef(Buf, GetACP); - end; - - //--------------------------------------------------------------------------- - - function KeyUnicode(C: Char): WideChar; - // Converts the given character into its corresponding Unicode character - // depending on the active keyboard layout. - begin - Result := C; //!!!!!! - end; - - //--------------- end local functions --------------------------------------- - -var - FoundMatch: Boolean; - NewChar: WideChar; - -begin - StopTimer(SearchTimer); - - if FIncrementalSearch <> isNone then - begin - if CharCode <> 0 then - begin - DoStateChange([tsIncrementalSearching]); - - // Convert the given virtual key code into a Unicode character based on the current locale. - NewChar := KeyUnicode(Char(CharCode)); - PreviousSearch := NewChar = WideChar(VK_BACK); - // We cannot do a search with an empty search buffer. - if not PreviousSearch or (FSearchBuffer <> '') then - begin - // Determine which method to use to advance nodes and the start node to search from. - case FSearchStart of - ssAlwaysStartOver: - Run := nil; - ssFocusedNode: - Run := FFocusedNode; - else // ssLastHit - Run := FLastSearchNode; - end; - - // Make sure the start node corresponds to the search criterion. - if Assigned(Run) then - begin - case FIncrementalSearch of - isInitializedOnly: - if not (vsInitialized in Run.States) then - Run := nil; - isVisibleOnly: - if not FullyVisible[Run] or IsEffectivelyFiltered[Run] then - Run := nil; - end; - end; - Stop := Run; - - // VK_BACK temporarily changes search direction to opposite mode. - if PreviousSearch then - begin - if SearchDirection = sdBackward then - SearchDirection := sdForward - else - SearchDirection := sdBackward; - end - else - SearchDirection := FSearchDirection; - // The "single letter mode" is used to advance quickly from node to node when pressing the same key several times. - SingleLetter := (Length(FSearchBuffer) = 1) and not PreviousSearch and (FSearchBuffer[1] = NewChar); - // However if the current hit (if there is one) would fit also with a repeated character then - // don't use single letter mode. - if SingleLetter and (DoIncrementalSearch(Run, FSearchBuffer + NewChar) = 0) then - SingleLetter := False; - SetupNavigation; - FoundMatch := False; - - if Assigned(Run) then - begin - if SingleLetter then - NewSearchText := FSearchBuffer - else - if PreviousSearch then - begin - SetLength(FSearchBuffer, Length(FSearchBuffer) - 1); - NewSearchText := FSearchBuffer; - end - else - NewSearchText := FSearchBuffer + NewChar; - - repeat - if DoIncrementalSearch(Run, NewSearchText) = 0 then - begin - FoundMatch := True; - Break; - end; - - // Advance to next node if we have not found a match. - Run := GetNextNode(Run); - // Do wrap around start or end of tree. - if (Run <> Stop) and (Run = nil) then - SetupNavigation; - until Run = Stop; - end; - - if FoundMatch then - begin - ClearSelection; - FSearchBuffer := NewSearchText; - FLastSearchNode := Run; - FocusedNode := Run; - Selected[Run] := True; - FLastSearchNode := Run; - end - else - // Play an acoustic signal if nothing could be found but don't beep if only the currently - // focused node matches. - if Assigned(Run) and (DoIncrementalSearch(Run, NewSearchText) <> 0) then - Beep; - end; - end; - - // Restart search timeout interval. - SetTimer(Handle, SearchTimer, FSearchTimeout, nil); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.HandleMouseDblClick(var Message: TWMMouse; const HitInfo: THitInfo); - -var - Node: PVirtualNode; - MayEdit: Boolean; - -begin - MayEdit := not (tsEditing in FStates) and (toEditOnDblClick in FOptions.MiscOptions); - if tsEditPending in FStates then - begin - StopTimer(EditTimer); - DoStateChange([], [tsEditPending]); - end; - - if not (tsEditing in FStates) or DoEndEdit then - begin - if HitInfo.HitColumn = FHeader.Columns.FClickIndex then - DoColumnDblClick(HitInfo.HitColumn, KeysToShiftState(Message.Keys)); - - if HitInfo.HitNode <> nil then - DoNodeDblClick(HitInfo); - - Node := nil; - if (hiOnItem in HitInfo.HitPositions) and (HitInfo.HitColumn > NoColumn) and - (coFixed in FHeader.Columns[HitInfo.HitColumn].Options) then - begin - if hiUpperSplitter in HitInfo.HitPositions then - Node := GetPreviousVisible(HitInfo.HitNode, True) - else - if hiLowerSplitter in HitInfo.HitPositions then - Node := HitInfo.HitNode; - end; - - if Assigned(Node) and (Node <> FRoot) and (toNodeHeightDblClickResize in FOptions.MiscOptions) then - begin - if DoNodeHeightDblClickResize(Node, HitInfo.HitColumn, KeysToShiftState(Message.Keys), Point(Message.XPos, Message.YPos)) then - begin - SetNodeHeight(Node, FDefaultNodeHeight); - UpdateWindow(Handle); - MayEdit := False; - end; - end - else - if hiOnItemCheckBox in HitInfo.HitPositions then - begin - HandleCheckboxClick(HitInfo.HitNode, Message.Keys); - MayEdit := False; - end// if hiOnItemCheckBox - else - begin - if hiOnItemButton in HitInfo.HitPositions then - begin - ToggleNode(HitInfo.HitNode); - MayEdit := False; - end - else - begin - if toToggleOnDblClick in FOptions.MiscOptions then - begin - if ((([hiOnItemButton, hiOnItemLabel, hiOnNormalIcon, hiOnStateIcon] * HitInfo.HitPositions) <> []) or - ((toFullRowSelect in FOptions.SelectionOptions) and Assigned(HitInfo.HitNode))) then - begin - ToggleNode(HitInfo.HitNode); - MayEdit := False; - end; - end; - end; - end; - end; - - if MayEdit and Assigned(FFocusedNode) and (FFocusedNode = HitInfo.HitNode) and - (FFocusedColumn = HitInfo.HitColumn) and CanEdit(FFocusedNode, HitInfo.HitColumn) then - begin - DoStateChange([tsEditPending]); - FEditColumn := FFocusedColumn; - SetTimer(Handle, EditTimer, 0, nil); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.HandleCheckboxClick(pHitNode: PVirtualNode; pKeys: LongInt); -var - NewCheckState: TCheckState; -begin - NewCheckState := DetermineNextCheckState(pHitNode.CheckType, pHitNode.CheckState); - if (ssLeft in KeysToShiftState(pKeys)) and DoChecking(pHitNode, NewCheckState) then - begin - if (Self.SelectedCount > 1) and (Selected[pHitNode]) and not (toSyncCheckboxesWithSelection in TreeOptions.SelectionOptions) then - SetCheckStateForAll(NewCheckState, True) - else - DoCheckClick(pHitNode, NewCheckState); - end;//if ssLeft -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.HandleMouseDown(var Message: TWMMouse; var HitInfo: THitInfo); - -// centralized mouse button down handling - -var - LastFocused: PVirtualNode; - Column: TColumnIndex; - ShiftState: TShiftState; - - // helper variables to shorten boolean equations/expressions - AutoDrag, // automatic (or allowed) drag start - IsLabelHit, // the node's caption or images are hit - IsCellHit, // for grid extension or full row select (but not check box, button) - IsAnyHit, // either IsHit or IsCellHit - IsHeightTracking, // height tracking - MultiSelect, // multiselection is enabled - ShiftEmpty, // ShiftState = [] - NodeSelected: Boolean; // the new node (if any) is selected - NewColumn: Boolean; // column changed - NewNode: Boolean; // Node changed. - NeedChangeEvent: Boolean; // change event is required for selection change - CanClear: Boolean; - AltPressed: Boolean; // Pressing the Alt key enables special processing for selection. - FullRowDrag: Boolean; // Start dragging anywhere within a node's bound. - NodeRect: TRect; - - //--------------- local functions ------------------------------------------- - - //Fix for issue: 310 whenever there is a need to invalidate a column, consider - //auto spanned columns if applicable - procedure invalidateWithAutoSpan(acolumn: TColumnIndex; anode: PVirtualNode); - var - NextColumn: Integer; - Dummy: TColumnIndex; - begin - if (not FHeader.UseColumns) or (not (toAutoSpanColumns in FOptions.AutoOptions)) - or (acolumn = FHeader.MainColumn) then - begin - //no need to find auto spanned next columns - InvalidateColumn(acolumn); - exit; - end; - //invalidate auto spanned columns too - with FHeader.Columns do //standard loop for auto span - begin - NextColumn := acolumn; - repeat - InvalidateColumn(NextColumn); - Dummy := GetNextVisibleColumn(NextColumn); - if (Dummy = InvalidColumn) or - not ColumnIsEmpty(anode, Dummy) - or - (Items[Dummy].BidiMode <> bdLeftToRight) then - Break; - NextColumn := Dummy; - until False; - end; - end; - - //--------------- end local functions --------------------------------------- - -begin - if [tsWheelPanning, tsWheelScrolling] * FStates <> [] then - begin - StopWheelPanning; - Exit; - end; - - if tsEditPending in FStates then - begin - StopTimer(EditTimer); - DoStateChange([], [tsEditPending]); - end; - - FLastHitInfo := HitInfo; // Save for later use in OnNodeClick event, see issue #692 - if (tsEditing in FStates) then begin - if not DoEndEdit then - exit; - // Repeat the hit test as an OnEdited event might got triggered that could modify the tree. - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - end;//if tsEditing - - // Focus change. Don't use the SetFocus method as this does not work for MDI Winapi.Windows. - if not Focused and CanFocus then - begin - Winapi.Windows.SetFocus(Handle); - // Repeat the hit test as an OnExit event might got triggered that could modify the tree. - GetHitTestInfoAt(Message.XPos, Message.YPos, True, HitInfo); - end; - - if IsEmpty then - exit; - - // Keep clicked column in case the application needs it. - FHeader.Columns.FClickIndex := HitInfo.HitColumn; - - // Change column only if we have hit the node label. - if (hiOnItemLabel in HitInfo.HitPositions) or - (toFullRowSelect in FOptions.SelectionOptions) or - (toGridExtensions in FOptions.MiscOptions) then - begin - NewColumn := FFocusedColumn <> HitInfo.HitColumn; - if toExtendedFocus in FOptions.SelectionOptions then - Column := HitInfo.HitColumn - else - Column := FHeader.MainColumn; - end - else - begin - NewColumn := False; - Column := FFocusedColumn; - end; - - if NewColumn and not FHeader.AllowFocus(Column) then - begin - NewColumn := False; - Column := FFocusedColumn; - end; - - NewNode := FFocusedNode <> HitInfo.HitNode; - - // Translate keys and filter out shift and control key. - ShiftState := KeysToShiftState(Message.Keys) * [ssShift, ssCtrl, ssAlt]; - if ssAlt in ShiftState then - begin - AltPressed := True; - // Remove the Alt key from the shift state. It is not meaningful there. - Exclude(ShiftState, ssAlt); - end - else - AltPressed := False; - - // Various combinations determine what states the tree enters now. - // We initialize shorthand variables to avoid the following expressions getting too large - // and to avoid repeative expensive checks. - IsLabelHit := not AltPressed and not (toSimpleDrawSelection in FOptions.SelectionOptions) and - ((hiOnItemLabel in HitInfo.HitPositions) or (hiOnNormalIcon in HitInfo.HitPositions)); - - IsCellHit := not IsLabelHit and Assigned(HitInfo.HitNode) and - ([hiOnItemButton, hiOnItemCheckBox, hiNoWhere] * HitInfo.HitPositions = []) and - ((toFullRowSelect in FOptions.SelectionOptions) or - ((toGridExtensions in FOptions.MiscOptions) and (HitInfo.HitColumn > NoColumn))); - - IsAnyHit := IsLabelHit or IsCellHit; - MultiSelect := toMultiSelect in FOptions.SelectionOptions; - ShiftEmpty := ShiftState = []; - NodeSelected := IsAnyHit and (vsSelected in HitInfo.HitNode.States); - - // Determine the Drag behavior. - if MultiSelect and not (toDisableDrawSelection in FOptions.SelectionOptions) then - begin - // We have MultiSelect and want to draw a selection rectangle. - // We will start a full row drag only in case a label was hit, - // otherwise a multi selection will start. - FullRowDrag := (toFullRowDrag in FOptions.MiscOptions) and IsCellHit and - not (hiNowhere in HitInfo.HitPositions) and - (NodeSelected or (hiOnItemLabel in HitInfo.HitPositions) or (hiOnNormalIcon in HitInfo.HitPositions)); - end - else // No MultiSelect, hence we can start a drag anywhere in the row. - FullRowDrag := toFullRowDrag in FOptions.MiscOptions; - - IsHeightTracking := (Message.Msg = WM_LBUTTONDOWN) and - (hiOnItem in HitInfo.HitPositions) and - ([hiUpperSplitter, hiLowerSplitter] * HitInfo.HitPositions <> []); - - // Dragging might be started in the inherited handler manually (which is discouraged for stability reasons) - // the test for manual mode is done below (after the focused node is set). - AutoDrag := ((DragMode = dmAutomatic) or Dragging) and (not IsCellHit or FullRowDrag); - - // Query the application to learn if dragging may start now (if set to dmManual). - if Assigned(HitInfo.HitNode) and not AutoDrag and (DragMode = dmManual) then - AutoDrag := DoBeforeDrag(HitInfo.HitNode, Column) and (FullRowDrag or IsLabelHit); - - // handle node height tracking - if IsHeightTracking then - begin - if hiUpperSplitter in HitInfo.HitPositions then - FHeightTrackNode := GetPreviousVisible(HitInfo.HitNode, True) - else - FHeightTrackNode := HitInfo.HitNode; - - if CanSplitterResizeNode(Point(Message.XPos, Message.YPos), FHeightTrackNode, HitInfo.HitColumn) then - begin - FHeightTrackColumn := HitInfo.HitColumn; - NodeRect := GetDisplayRect(FHeightTrackNode, FHeightTrackColumn, False); - FHeightTrackPoint := Point(NodeRect.Left, NodeRect.Top); - DoStateChange([tsNodeHeightTrackPending]); - Exit; - end; - end; - - // handle button clicks - if (hiOnItemButton in HitInfo.HitPositions) and (vsHasChildren in HitInfo.HitNode.States) then - begin - ToggleNode(HitInfo.HitNode); - Exit; - end; - - // check event - if hiOnItemCheckBox in HitInfo.HitPositions then - begin - HandleCheckboxClick(HitInfo.HitNode, Message.Keys); - Exit; - end; - - // Keep this node's level in case we need it for constraint selection. - if (FRoot.ChildCount > 0) and ShiftEmpty or (FSelectionCount = 0) then - if Assigned(HitInfo.HitNode) then - FLastSelectionLevel := GetNodeLevelForSelectConstraint(HitInfo.HitNode) - else - FLastSelectionLevel := GetNodeLevelForSelectConstraint(GetLastVisibleNoInit(nil, True)); - - // immediate clearance - // Determine for the right mouse button if there is a popup menu. In this case and if drag'n drop is pending - // the current selection has to stay as it is. - with HitInfo, Message do - CanClear := not AutoDrag and - (not (tsRightButtonDown in FStates) or not HasPopupMenu(HitNode, HitColumn, Point(XPos, YPos))); - - // pending clearance - if MultiSelect and ShiftEmpty and not (hiOnItemCheckbox in HitInfo.HitPositions) and IsAnyHit and AutoDrag and - NodeSelected and not FSelectionLocked - then - DoStateChange([tsClearPending]); - - // User starts a selection with a selection rectangle. - if not (toDisableDrawSelection in FOptions.SelectionOptions) and not (IsLabelHit or FullRowDrag) and MultiSelect then - begin - SetCapture(Handle); - DoStateChange([tsDrawSelPending]); - FDrawSelShiftState := ShiftState; - FNewSelRect := Rect(Message.XPos + FEffectiveOffsetX, Message.YPos - FOffsetY, Message.XPos + FEffectiveOffsetX, - Message.YPos - FOffsetY); - FLastSelRect := Rect(0, 0, 0, 0); - end; - - NeedChangeEvent := FSelectionCount >= 1; - if not FSelectionLocked and ((not (IsAnyHit or FullRowDrag) and MultiSelect and ShiftEmpty) or - (IsAnyHit and (not NodeSelected or (NodeSelected and CanClear)) and (ShiftEmpty or not MultiSelect or (tsRightButtonDown in FStates)))) then - begin - Assert(not (tsClearPending in FStates), 'Pending and direct clearance are mutual exclusive!'); - - // If the currently hit node was already selected then we have to reselect it again after clearing the current - // selection, but without a change event if it is the only selected node. - // The same applies if the Alt key is pressed, which allows to start drawing the selection rectangle also - // on node captions and images. Here the previous selection state does not matter, though. - if NodeSelected or (AltPressed and Assigned(HitInfo.HitNode) and (HitInfo.HitColumn = FHeader.MainColumn)) and not (hiNowhere in HitInfo.HitPositions) then - begin - InternalClearSelection; - InternalAddToSelection(HitInfo.HitNode, True); - if NeedChangeEvent then - begin - Invalidate; - Change(nil); - end; - end - else if (toAlwaysSelectNode in Self.TreeOptions.SelectionOptions) then - begin - if not (hiNowhere in HitInfo.HitPositions) then - ClearSelection(False) - else - if not (ssCtrl in ShiftState) then - DoStateChange([tsClearOnNewSelection], []); - end - else - ClearSelection(False); - end; - - // pending node edit - if Focused and - ((hiOnItemLabel in HitInfo.HitPositions) or ((toGridExtensions in FOptions.MiscOptions) and - (hiOnItem in HitInfo.HitPositions))) and NodeSelected and not NewColumn and ShiftEmpty and (SelectedCount = 1) then - begin - DoStateChange([tsEditPending]); - end; - - if not (toDisableDrawSelection in FOptions.SelectionOptions) - and not (IsLabelHit or FullRowDrag) and (MultiSelect or (hiNowhere in HitInfo.HitPositions)) then - begin - // The original code here was moved up to fix issue #187. - // In order not to break the semantics of this procedure, we are leaving these if statements here - if not IsCellHit then begin - if NeedChangeEvent then - Change(nil); - Exit; - end; - end; - - // Keep current mouse position. - FLastClickPos := Point(Message.XPos, Message.YPos); - - // Handle selection and node focus change. - if (IsLabelHit or IsCellHit) and - DoFocusChanging(FFocusedNode, HitInfo.HitNode, FFocusedColumn, Column) then - begin - if NewColumn then - begin - - if not Assigned(FFocusedNode) then - InvalidateColumn(FFocusedColumn) - else - invalidateWithAutoSpan(FFocusedColumn, FFocusedNode); //fix: issue 310 - if not Assigned(HitInfo.HitNode) then - InvalidateColumn(Column) - else - invalidateWithAutoSpan(Column, HitInfo.HitNode); //fix: issue 310 - FFocusedColumn := Column; - end; - if DragKind = dkDock then - begin - StopTimer(ScrollTimer); - DoStateChange([], [tsScrollPending, tsScrolling]); - end; - // Get the currently focused node to make multiple multi-selection blocks possible. - LastFocused := FFocusedNode; - if NewNode then - DoFocusNode(HitInfo.HitNode, False); - - if MultiSelect and not ShiftEmpty and not (tsRightButtonDown in FStates) then - HandleClickSelection(LastFocused, HitInfo.HitNode, ShiftState, AutoDrag) - else - begin - if ShiftEmpty then - FRangeAnchor := HitInfo.HitNode; - - // If the hit node is not yet selected then do it now. - if not NodeSelected then - AddToSelection(HitInfo.HitNode, True); - end; - - if NewNode or NewColumn then - begin - ScrollIntoView(FFocusedNode, False, - not (toDisableAutoscrollOnFocus in FOptions.AutoOptions) - and not (toFullRowSelect in FOptions.SelectionOptions)); - - DoFocusChange(FFocusedNode, FFocusedColumn); - end; - end; - - if (SelectedCount = 0) and NeedChangeEvent then - Change(nil); - - // Drag'n drop initiation - // If we lost focus in the interim the button states would be cleared in WM_KILLFOCUS. - if AutoDrag and IsAnyHit and (FStates * [tsLeftButtonDown, tsRightButtonDown, tsMiddleButtonDown] <> []) then - BeginDrag(False); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.HandleMouseUp(var Message: TWMMouse; const HitInfo: THitInfo); - -// Counterpart to the mouse down handler. - -var - ReselectFocusedNode: Boolean; - -begin - ReleaseCapture; - - if not (tsVCLDragPending in FStates) then - begin - // reset pending or persistent states - if IsMouseSelecting then - begin - DoStateChange([], [tsDrawSelecting, tsDrawSelPending, tsToggleFocusedSelection, tsClearOnNewSelection]); - Invalidate; - end; - - if tsClearPending in FStates then - begin - ReselectFocusedNode := Assigned(FFocusedNode) and (vsSelected in FFocusedNode.States); - ClearSelection; - if ReselectFocusedNode then - AddToSelection(FFocusedNode, False); - end; - - if (tsToggleFocusedSelection in FStates) and (HitInfo.HitNode = FFocusedNode) and Assigned(HitInfo.HitNode) then //Prevent AV when dereferencing HitInfo.HitNode below, see bug #100 - begin - if vsSelected in HitInfo.HitNode.States then - begin - if not (toAlwaysSelectNode in TreeOptions.SelectionOptions) or (Self.SelectedCount > 1) then - RemoveFromSelection(HitInfo.HitNode); - end - else - AddToSelection(HitInfo.HitNode, False); - end; - - DoStateChange([], [tsOLEDragPending, tsOLEDragging, tsClearPending, tsDrawSelPending, tsToggleFocusedSelection, - tsScrollPending, tsScrolling]); - StopTimer(ScrollTimer); - - if (FHeader.Columns.FClickIndex > NoColumn) and (FHeader.Columns.FClickIndex = HitInfo.HitColumn) then - DoColumnClick(HitInfo.HitColumn, KeysToShiftState(Message.Keys)); - - if FLastHitInfo.HitNode <> nil then begin // Use THitInfo of mouse down here, see issue #692 - DoNodeClick(FLastHitInfo); - if Assigned(FLastHitInfo.HitNode) then begin - InvalidateNode(FLastHitInfo.HitNode); - FLastHitInfo.HitNode := nil; // prevent firing the event again - end;//if - end; - - // handle a pending edit event - if tsEditPending in FStates then - begin - // Is the mouse still over the same node? - if (HitInfo.HitNode = FFocusedNode) and (hiOnItem in HitInfo.HitPositions) and - (toEditOnClick in FOptions.MiscOptions) and (FFocusedColumn = HitInfo.HitColumn) and - CanEdit(FFocusedNode, HitInfo.HitColumn) then - begin - FEditColumn := FFocusedColumn; - SetTimer(Handle, EditTimer, FEditDelay, nil); - end - else - DoStateChange([], [tsEditPending]); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.HasImage(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex): Boolean; - -// Determines whether the given node has got an image of the given kind in the given column. -// Returns True if so, otherwise False. -// The given node will be implicitly initialized if needed. - -var - Ghosted: Boolean; - Index: TImageIndex; - -begin - if not (vsInitialized in Node.States) then - InitNode(Node); - - Index := -1; - Ghosted := False; - DoGetImageIndex(Node, Kind, Column, Ghosted, Index); - Result := Index > -1; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.HasPopupMenu(Node: PVirtualNode; Column: TColumnIndex; Pos: TPoint): Boolean; - -// Determines whether the tree got a popup menu, either in its PopupMenu property, via the OnGetPopupMenu event or -// through inheritance. The latter case must be checked by the descendant which must override this method. - -begin - Result := Assigned(PopupMenu) or Assigned(DoGetPopupMenu(Node, Column, Pos)); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InitChildren(Node: PVirtualNode); - -// Initiates the initialization of the child number of the given node. - -var - Count: Cardinal; - -begin - if Assigned(Node) and (Node <> FRoot) and (vsHasChildren in Node.States) then - begin - Count := Node.ChildCount; - if DoInitChildren(Node, Count) then - begin - SetChildCount(Node, Count); - if Count = 0 then - Exclude(Node.States, vsHasChildren); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InitNode(Node: PVirtualNode); - -// Initiates the initialization of the given node to allow the application to load needed data for it. - -var - InitStates: TVirtualNodeInitStates; - MustAdjustInternalVariables: Boolean; - ParentCheckState, SelfCheckState: TCheckState; -begin - with Node^ do - begin - Include(States, vsInitializing); - try - InitStates := []; - if vsInitialized in States then - Include(InitStates, ivsReInit); - Include(States, vsInitialized); - if Parent = FRoot then - DoInitNode(nil, Node, InitStates) - else - DoInitNode(Parent, Node, InitStates); - - // Fix: Any parent check state must be propagated here. - // Because the CheckType is normally set in DoInitNode - // by the App. - if (Node.CheckType = ctTriStateCheckBox) and (toAutoTristateTracking in FOptions.AutoOptions) then - begin - ParentCheckState := Self.GetCheckState(Node.Parent); - SelfCheckState := Self.GetCheckState(Node); - if ((ParentCheckState = csCheckedNormal) - or (ParentCheckState = csUncheckedNormal)) - and (not SelfCheckState.IsDisabled()) - and (SelfCheckState <> ParentCheckState) - and (Parent <> FRoot) - then - SetCheckState(Node, Node.Parent.CheckState); - end - else if (toSyncCheckboxesWithSelection in TreeOptions.SelectionOptions) then - Node.CheckType := TCheckType.ctCheckBox; - - if ivsDisabled in InitStates then - Include(States, vsDisabled); - if ivsHasChildren in InitStates then - Include(States, vsHasChildren); - if ivsSelected in InitStates then - InternalAddToSelection(Node, False); - if ivsMultiline in InitStates then - Include(States, vsMultiline); - if ivsFiltered in InitStates then - begin - MustAdjustInternalVariables := not ((ivsReInit in InitStates) and (vsFiltered in States)); - - Include(States, vsFiltered); - - if not (toShowFilteredNodes in FOptions.PaintOptions) and MustAdjustInternalVariables then - begin - AdjustTotalHeight(Node, -NodeHeight, True); - if FullyVisible[Node] then - Dec(FVisibleCount); - if FUpdateCount = 0 then - UpdateScrollBars(True); - end; - end; - - // Expanded may already be set (when called from ReinitNode) or be set in DoInitNode, allow both. - if (vsExpanded in Node.States) xor (ivsExpanded in InitStates) then - begin - // Expand node if not yet done (this will automatically initialize child nodes). - if ivsExpanded in InitStates then - ToggleNode(Node) - else - // If the node already was expanded then explicitly trigger child initialization. - if vsHasChildren in Node.States then - InitChildren(Node); - end; - finally - Exclude(States, vsInitializing); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InternalAddFromStream(Stream: TStream; Version: Integer; Node: PVirtualNode); - -// Loads all details for Node (including its children) from the given stream. -// Because the new nodes might be selected this method also fixes the selection array. - -var - Stop: PVirtualNode; - Index: Integer; - LastTotalHeight: Cardinal; - WasFullyVisible: Boolean; - -begin - Assert(Node <> FRoot, 'The root node cannot be loaded from stream.'); - - // Keep the current total height value of Node as it has already been applied - // but might change in the load and fixup code. We have to adjust that afterwards. - LastTotalHeight := Node.TotalHeight; - WasFullyVisible := FullyVisible[Node] and not IsEffectivelyFiltered[Node]; - - // Read in the new nodes. - ReadNode(Stream, Version, Node); - - // One time update of node-internal states and the global visibility counter. - // This is located here to ease and speed up the loading process. - FixupTotalCount(Node); - AdjustTotalCount(Node.Parent, Node.TotalCount - 1, True); // -1 because Node itself was already set. - FixupTotalHeight(Node); - AdjustTotalHeight(Node.Parent, Integer(Node.TotalHeight) - Integer(LastTotalHeight), True); - - // New nodes are always visible, so the visible node count has been increased already. - // If Node is now invisible we have to take back this increment and don't need to add any visible child node. - if not FullyVisible[Node] or IsEffectivelyFiltered[Node] then - begin - if WasFullyVisible then - Dec(FVisibleCount); - end - else - // It can never happen that the node is now fully visible but was not before as this would require - // that the visibility state of one of its parents has changed, which cannot happen during loading. - Inc(FVisibleCount, CountVisibleChildren(Node)); - - // Fix selection array. - ClearTempCache; - if Node = FRoot then - Stop := nil - else - Stop := Node.NextSibling; - - if toMultiSelect in FOptions.SelectionOptions then - begin - // Add all nodes which were selected before to the current selection (unless they are already there). - while Node <> Stop do - begin - if (vsSelected in Node.States) and not FindNodeInSelection(Node, Index, 0, High(FSelection)) then - InternalCacheNode(Node); - Node := GetNextNoInit(Node); - end; - if FTempNodeCount > 0 then - AddToSelection(FTempNodeCache, FTempNodeCount, True); - ClearTempCache; - end - else // No further selected nodes allowed so delete the corresponding flag in all new nodes. - while Node <> Stop do - begin - Exclude(Node.States, vsSelected); - Node := GetNextNoInit(Node); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.InternalAddToSelection(Node: PVirtualNode; ForceInsert: Boolean): Boolean; -var - lSingletonNodeArray: TNodeArray; -begin - Assert(Assigned(Node), 'Node must not be nil!'); - SetLength(lSingletonNodeArray, 1); - lSingletonNodeArray[0] := Node; - Result := InternalAddToSelection(lSingletonNodeArray, 1, ForceInsert); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.InternalAddToSelection(const NewItems: TNodeArray; NewLength: Integer; - ForceInsert: Boolean): Boolean; - -// Internal version of method AddToSelection which does not trigger OnChange events - -var - I, J: Integer; - CurrentEnd: Integer; - Constrained, - SiblingConstrained: Boolean; - lPreviousSelectedCount: Integer; - AddedNodesSize: Integer; - PTmpNode: PVirtualNode; - -begin - lPreviousSelectedCount := FSelectionCount; - // The idea behind this code is to use a kind of reverse merge sort. QuickSort is quite fast - // and would do the job here too but has a serious problem with already sorted lists like FSelection. - - // current number of valid entries - AddedNodesSize := 0; - - // 1) Remove already selected items, mark all other as being selected. - if ForceInsert then - begin - //Fix: For already selected node when selected, this path - //is used that didn't contain the Constraint logic. Added. - Constrained := toLevelSelectConstraint in FOptions.SelectionOptions; - if Constrained and (FLastSelectionLevel = -1) then - FLastSelectionLevel := GetNodeLevelForSelectConstraint(NewItems[0]); - AddedNodesSize := NewLength; - end - else - begin - Constrained := toLevelSelectConstraint in FOptions.SelectionOptions; - if Constrained and (FLastSelectionLevel = -1) then - FLastSelectionLevel := GetNodeLevelForSelectConstraint(NewItems[0]); - SiblingConstrained := toSiblingSelectConstraint in FOptions.SelectionOptions; - if SiblingConstrained and (FRangeAnchor = nil) then - FRangeAnchor := NewItems[0]; - - for I := 0 to NewLength - 1 do - if ([vsSelected, vsDisabled] * NewItems[I].States <> []) or - (Constrained and (Cardinal(FLastSelectionLevel) <> GetNodeLevel(NewItems[I]))) or - (SiblingConstrained and (FRangeAnchor.Parent <> NewItems[I].Parent)) - then - Inc(PAnsiChar(NewItems[I])) // mark as invalid by setting the LSB - else - Inc(AddedNodesSize); - end; - - I := PackArray(NewItems, NewLength); - if I > -1 then - NewLength := I; - - Result := NewLength > 0; - if Result then - begin - // 2) Sort the new item list so we can easily traverse it. - if NewLength > 1 then - QuickSort(NewItems, 0, NewLength - 1); - // 3) Make room in FSelection for the new items. - if lPreviousSelectedCount + NewLength >= Length(FSelection) then - SetLength(FSelection, lPreviousSelectedCount + NewLength); - - // 4) Merge in new items - J := NewLength - 1; - CurrentEnd := lPreviousSelectedCount - 1; - - while J >= 0 do - begin - // First insert all new entries which are greater than the greatest entry in the old list. - // If the current end marker is < 0 then there's nothing more to move in the selection - // array and only the remaining new items must be inserted. - if CurrentEnd >= 0 then - begin - while (J >= 0) and (PAnsiChar(NewItems[J]) > PAnsiChar(FSelection[CurrentEnd])) do - begin - FSelection[CurrentEnd + J + 1] := NewItems[J]; - Dec(J); - end; - // early out if nothing more needs to be copied - if J < 0 then - Break; - end - else - begin - // insert remaining new entries at position 0 - Move(NewItems[0], FSelection[0], (J + 1) * SizeOf(Pointer)); - // nothing more to do so exit main loop - Break; - end; - - // find the last entry in the remaining selection list which is smaller then the largest - // entry in the remaining new items list - FindNodeInSelection(NewItems[J], I, 0, CurrentEnd); - Dec(I); - // move all entries which are greater than the greatest entry in the new items list up - // so the remaining gap travels down to where new items must be inserted - Move(FSelection[I + 1], FSelection[I + J + 2], (CurrentEnd - I) * SizeOf(Pointer)); - CurrentEnd := I; - end; - - // update selection count - Inc(FSelectionCount, AddedNodesSize); - - // post process added nodes - // First set vsSelected flag for all newly selected nodes, then fire event - for I := 0 to AddedNodesSize - 1 do - Include(NewItems[I].States, vsSelected); - - for I := 0 to AddedNodesSize - 1 do - begin - PTmpNode := NewItems[I]; - // call on add event callbackevent - if Assigned(FOnAddToSelection) then - FOnAddToSelection(Self, PTmpNode); - if SyncCheckstateWithSelection[PTmpNode] then - checkstate[PTmpNode] := csCheckedNormal; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InternalCacheNode(Node: PVirtualNode); - -// Adds the given node to the temporary node cache (used when collecting possibly large amounts of nodes). - -var - Len: Cardinal; - -begin - Len := Length(FTempNodeCache); - if FTempNodeCount = Len then - begin - if Len < 100 then - Len := 100 - else - Len := Len + Len div 10; - SetLength(FTempNodeCache, Len); - end; - FTempNodeCache[FTempNodeCount] := Node; - Inc(FTempNodeCount); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InternalClearSelection(); - -var - Count: Integer; - lNode: PVirtualNode; -begin - // It is possible that there are invalid node references in the selection array - // if the tree update is locked and changes in the structure were made. - // Handle this potentially dangerous situation by packing the selection array explicitely. - if IsUpdating then - begin - Count := PackArray(FSelection, FSelectionCount); - if Count > -1 then - begin - FSelectionCount := Count; - SetLength(FSelection, FSelectionCount); - end; - end; - - while FSelectionCount > 0 do - begin - Dec(FSelectionCount); - lNode := FSelection[FSelectionCount]; - //sync path note: deselect when click on another or on outside area - Exclude(lNode.States, vsSelected); - if SyncCheckstateWithSelection[lNode] then - CheckState[lNode] := csUncheckedNormal; - DoRemoveFromSelection(lNode); - end; - ResetRangeAnchor; - FSelection := nil; - DoStateChange([], [tsClearPending]); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InternalConnectNode(Node, Destination: PVirtualNode; Target: TBaseVirtualTree; - Mode: TVTNodeAttachMode); - -// Connects Node with Destination depending on Mode. -// No error checking takes place. Node as well as Destination must be valid. Node must never be a root node and -// Destination must not be a root node if Mode is amInsertBefore or amInsertAfter. - -var - Run: PVirtualNode; - -begin - // Keep in mind that the destination node might belong to another tree. - with Target do - begin - case Mode of - amInsertBefore: - begin - Node.PrevSibling := Destination.PrevSibling; - Destination.PrevSibling := Node; - Node.NextSibling := Destination; - Node.Parent := Destination.Parent; - Node.Index := Destination.Index; - if Node.PrevSibling = nil then - Node.Parent.FirstChild := Node - else - Node.PrevSibling.NextSibling := Node; - - // reindex all following nodes - Run := Destination; - while Assigned(Run) do - begin - Inc(Run.Index); - Run := Run.NextSibling; - end; - end; - amInsertAfter: - begin - Node.NextSibling := Destination.NextSibling; - Destination.NextSibling := Node; - Node.PrevSibling := Destination; - Node.Parent := Destination.Parent; - if Node.NextSibling = nil then - Node.Parent.LastChild := Node - else - Node.NextSibling.PrevSibling := Node; - Node.Index := Destination.Index; - - // reindex all following nodes - Run := Node; - while Assigned(Run) do - begin - Inc(Run.Index); - Run := Run.NextSibling; - end; - end; - amAddChildFirst: - begin - if Assigned(Destination.FirstChild) then - begin - // If there's a first child then there must also be a last child. - Destination.FirstChild.PrevSibling := Node; - Node.NextSibling := Destination.FirstChild; - Destination.FirstChild := Node; - end - else - begin - // First child node at this location. - Destination.FirstChild := Node; - Destination.LastChild := Node; - Node.NextSibling := nil; - end; - Node.PrevSibling := nil; - Node.Parent := Destination; - Node.Index := 0; - // reindex all following nodes - Run := Node.NextSibling; - while Assigned(Run) do - begin - Inc(Run.Index); - Run := Run.NextSibling; - end; - end; - amAddChildLast: - begin - if Assigned(Destination.LastChild) then - begin - // If there's a last child then there must also be a first child. - Destination.LastChild.NextSibling := Node; - Node.PrevSibling := Destination.LastChild; - Destination.LastChild := Node; - end - else - begin - // first child node at this location - Destination.FirstChild := Node; - Destination.LastChild := Node; - Node.PrevSibling := nil; - end; - Node.NextSibling := nil; - Node.Parent := Destination; - if Assigned(Node.PrevSibling) then - Node.Index := Node.PrevSibling.Index + 1 - else - Node.Index := 0; - end; - else - // amNoWhere: do nothing - end; - // Remove temporary states. - Node.States := Node.States - [vsChecking, vsCutOrCopy, vsDeleting]; - - if (Mode <> amNoWhere) then begin - Inc(Node.Parent.ChildCount); - Include(Node.Parent.States, vsHasChildren); - AdjustTotalCount(Node.Parent, Node.TotalCount, True); - - // Add the new node's height only if its parent is expanded. - if (vsExpanded in Node.Parent.States) and (vsVisible in Node.States) then begin - AdjustTotalHeight(Node.Parent, Node.TotalHeight, True); - Inc(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1))); - end;//if - - // Update the hidden children flag of the parent. - if (Node.Parent <> FRoot) then - begin - // If we have added a visible node then simply remove the all-children-hidden flag. - if IsEffectivelyVisible[Node] then - Exclude(Node.Parent.States, vsAllChildrenHidden) - else begin - // If we have added an invisible node and this is the only child node then - // make sure the all-children-hidden flag is in a determined state. - // If there were child nodes before then no action is needed. - if Node.Parent.ChildCount = 1 then - Include(Node.Parent.States, vsAllChildrenHidden); - end;//else - end; //if Node.Parent <> FRoot - end;//if Mode <> amNoWhere - end;//With -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.InternalData(Node: PVirtualNode): Pointer; - -begin - Result := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InternalDisconnectNode(Node: PVirtualNode; KeepFocus: Boolean; Reindex: Boolean = True; ParentClearing: Boolean = False); - -// Disconnects the given node from its parent and siblings. The node's pointer are not reset so they can still be used -// after return from this method (probably a very short time only!). -// If KeepFocus is True then the focused node is not reset. This is useful if the given node is reconnected to the tree -// immediately after return of this method and should stay being the focused node if it was it before. -// Note: Node must not be nil or the root node. - -var - Parent, - Run: PVirtualNode; - Index: Integer; - AdjustHeight: Boolean; - -begin - Assert(Assigned(Node) and (Node <> FRoot), 'Node must neither be nil nor the root node.'); - - if (Node = FFocusedNode) and not KeepFocus then - begin - DoFocusNode(nil, False); - DoFocusChange(FFocusedNode, FFocusedColumn); - end; - - if Node = FRangeAnchor then - ResetRangeAnchor; - - // Update the hidden children flag of the parent. - if (Node.Parent <> FRoot) and not (ParentClearing) then - if FUpdateCount = 0 then - DetermineHiddenChildrenFlag(Node.Parent) - else - Include(FStates, tsUpdateHiddenChildrenNeeded); - - if not (vsDeleting in Node.States) then - begin - // Some states are only temporary so take them out. - Node.States := Node.States - [vsChecking]; - Parent := Node.Parent; - Dec(Parent.ChildCount); - AdjustHeight := (vsExpanded in Parent.States) and (vsVisible in Node.States); - if Parent.ChildCount = 0 then - begin - Parent.States := Parent.States - [vsAllChildrenHidden, vsHasChildren]; - if (Parent <> FRoot) and (vsExpanded in Parent.States) then - Exclude(Parent.States, vsExpanded); - end; - AdjustTotalCount(Parent, -Integer(Node.TotalCount), True); - if AdjustHeight then - AdjustTotalHeight(Parent, -Integer(Node.TotalHeight), True); - if FullyVisible[Node] then - Dec(FVisibleCount, CountVisibleChildren(Node) + Cardinal(IfThen(IsEffectivelyVisible[Node], 1))); - - if Assigned(Node.PrevSibling) then - Node.PrevSibling.NextSibling := Node.NextSibling - else - Parent.FirstChild := Node.NextSibling; - - if Assigned(Node.NextSibling) then - begin - Node.NextSibling.PrevSibling := Node.PrevSibling; - // Reindex all following nodes. - if Reindex then - begin - Run := Node.NextSibling; - Index := Node.Index; - while Assigned(Run) do - begin - Run.Index := Index; - Inc(Index); - Run := Run.NextSibling; - end; - end; - end - else - Parent.LastChild := Node.PrevSibling; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InternalRemoveFromSelection(Node: PVirtualNode); - -// Special version to mark a node to be no longer in the current selection. PackArray must -// be used to remove finally those entries. - -var - Index: Integer; - -begin - // Because pointers are always DWORD aligned we can simply increment all those - // which we want to have removed (see also PackArray) and still have the - // order in the list preserved. - if FindNodeInSelection(Node, Index, -1, -1) then - begin - //sync path note: deselect when overlapping drawselection is made - Exclude(Node.States, vsSelected); - if SyncCheckstateWithSelection[Node] then - Node.CheckState := csUncheckedNormal; // Avoid using SetCheckState() as it handles toSyncCheckboxesWithSelection as well. - Inc(PAnsiChar(FSelection[Index])); - DoRemoveFromSelection(Node); - Change(Node); // Calling Change() here fixes issue #1047 - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InvalidateCache; - -// Marks the cache as invalid. - -begin - DoStateChange([tsValidationNeeded], [tsUseCache]); - //ChangeTreeStatesAsync([csValidationNeeded], [csUseCache]); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.MarkCutCopyNodes; - -// Sets the vsCutOrCopy style in every currently selected but not disabled node to indicate it is -// now part of a clipboard operation. - -var - Nodes: TNodeArray; - I: Integer; - -begin - Nodes := nil; - if FSelectionCount > 0 then - begin - // need the current selection sorted to exclude selected nodes which are children, grandchildren etc. of - // already selected nodes - Nodes := GetSortedSelection(False); - for I := 0 to High(Nodes) do - with Nodes[I]^ do - if not (vsDisabled in States) then - Include(States, vsCutOrCopy); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.Loaded; - -var - LastRootCount: Cardinal; - IsReadOnly: Boolean; - -begin - inherited; - - // Call RegisterDragDrop after all visual inheritance changes to MiscOptions have been applied. - if not (csDesigning in ComponentState) and (toAcceptOLEDrop in FOptions.MiscOptions) then - if HandleAllocated then - RegisterDragDrop(Handle, DragManager as IDropTarget); - - // If a root node count has been set during load of the tree then update its child structure now - // as this hasn't been done yet in this case. - if (tsNeedRootCountUpdate in FStates) and (FRoot.ChildCount > 0) then - begin - DoStateChange([], [tsNeedRootCountUpdate]); - IsReadOnly := toReadOnly in FOptions.MiscOptions; - FOptions.InternalSetMiscOptions(FOptions.MiscOptions - [toReadOnly]); - LastRootCount := FRoot.ChildCount; - FRoot.ChildCount := 0; - BeginUpdate; - SetChildCount(FRoot, LastRootCount); - EndUpdate; - if IsReadOnly then - FOptions.InternalSetMiscOptions(FOptions.MiscOptions + [toReadOnly]); - end; - - // Prevent the object inspector at design time from marking the header as being modified - // when auto resize is enabled. - Updating; - try - FHeader.UpdateMainColumn; - FHeader.Columns.FixPositions; - if toAutoBidiColumnOrdering in FOptions.AutoOptions then - FHeader.Columns.ReorderColumns(UseRightToLeftAlignment); - // Because of the special recursion and update stopper when creating the window (or resizing it) - // we have to manually trigger the auto size calculation here. - if hsNeedScaling in FHeader.States then - FHeader.RescaleHeader - else - FHeader.RecalculateHeader; - if hoAutoResize in FHeader.Options then - FHeader.Columns.AdjustAutoSize(InvalidColumn, True); - finally - Updated; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.MainColumnChanged; - -begin - DoCancelEdit; - - if Assigned(FAccessibleItem) then - NotifyWinEvent(EVENT_OBJECT_NAMECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.MouseMove(Shift: TShiftState; X, Y: Integer); - -var - R: TRect; - -begin - if tsNodeHeightTrackPending in FStates then - begin - // Remove hint if shown currently. - Application.CancelHint; - - // Stop wheel panning if active. - StopWheelPanning; - - // Stop timers - StopTimer(ExpandTimer); - StopTimer(EditTimer); - StopTimer(HeaderTimer); - StopTimer(ScrollTimer); - StopTimer(SearchTimer); - FSearchBuffer := ''; - FLastSearchNode := nil; - - DoStateChange([tsNodeHeightTracking], [tsScrollPending, tsScrolling, tsEditPending, tsOLEDragPending, tsVCLDragPending, - tsIncrementalSearching, tsNodeHeightTrackPending]); - end; - - if tsDrawSelPending in FStates then - begin - // Remove current selection in case the user clicked somewhere in the window (but not a node) - // and moved the mouse. - if CalculateSelectionRect(X, Y) then - begin - InvalidateRect(Handle, @FNewSelRect, False); - UpdateWindow(Handle); - if (Abs(FNewSelRect.Right - FNewSelRect.Left) > Mouse.DragThreshold) or - (Abs(FNewSelRect.Bottom - FNewSelRect.Top) > Mouse.DragThreshold) then - begin - if tsClearPending in FStates then - begin - DoStateChange([], [tsClearPending]); - ClearSelection; - end; - DoStateChange([tsDrawSelecting], [tsDrawSelPending]); - - // Reset to main column for multiselection. - FocusedColumn := FHeader.MainColumn; - - // The current rectangle may already include some node captions. Handle this. - if HandleDrawSelection(X, Y) then - InvalidateRect(Handle, nil, False); - end; - end; - end - else - begin - if tsNodeHeightTracking in FStates then - begin - // Handle height tracking. - if DoNodeHeightTracking(FHeightTrackNode, FHeightTrackColumn, FHeader.GetShiftState, - FHeightTrackPoint, Point(X, Y)) then - begin - // Avoid negative (or zero) node heights. - if FHeightTrackPoint.Y >= Y then - Y := FHeightTrackPoint.Y + 1; - SetNodeHeight(FHeightTrackNode, Y - FHeightTrackPoint.Y); - UpdateWindow(Handle); - Exit; - end; - end; - - // If both wheel panning and auto scrolling are pending then the user moved the mouse while holding down the - // middle mouse button. This means panning is being used, hence remove the wheel scroll flag. - if [tsWheelPanning, tsWheelScrolling] * FStates = [tsWheelPanning, tsWheelScrolling] then - begin - if ((Abs(FLastClickPos.X - X) >= Mouse.DragThreshold) or (Abs(FLastClickPos.Y - Y) >= Mouse.DragThreshold)) then - DoStateChange([], [tsWheelScrolling]); - end; - - // Really start dragging if the mouse has been moved more than the threshold. - if (tsOLEDragPending in FStates) and - ( - ((Abs(FLastClickPos.X - X) >= FDragThreshold) and (X > 0)) or // Check >0 to fix issue #833 - ((Abs(FLastClickPos.Y - Y) >= FDragThreshold) and (Y > 0)) - ) - then - DoDragging(FLastClickPos) - else - begin - if CanAutoScroll then - DoAutoScroll(X, Y); - if [tsWheelPanning, tsWheelScrolling] * FStates <> [] then - AdjustPanningCursor(X, Y); - if not IsMouseSelecting then - begin - HandleHotTrack(X, Y); - inherited MouseMove(Shift, X, Y); - end - else - begin - // Handle draw selection if required, but don't do the work twice if the - // auto scrolling code already cares about the selection. - if not (tsScrolling in FStates) and CalculateSelectionRect(X, Y) then - begin - // If something in the selection changed then invalidate the entire - // tree instead trying to figure out the display rects of all changed nodes. - if HandleDrawSelection(X, Y) then - InvalidateRect(Handle, nil, False) - else - begin - UnionRect(R, OrderRect(FNewSelRect), OrderRect(FLastSelRect)); - OffsetRect(R, -FEffectiveOffsetX, FOffsetY); - InvalidateRect(Handle, @R, False); - end; - UpdateWindow(Handle); - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.Notification(AComponent: TComponent; Operation: TOperation); - -begin - if (AComponent <> Self) and (Operation = opRemove) then - begin - // Check for components linked to the tree. - if AComponent = FImages then - begin - Images := nil; - if not (csDestroying in ComponentState) then - Invalidate; - end - else - if AComponent = FStateImages then - begin - StateImages := nil; - if not (csDestroying in ComponentState) then - Invalidate; - end - else - if AComponent = FCustomCheckImages then - begin - CustomCheckImages := nil; - FCheckImageKind := ckSystemDefault; - if not (csDestroying in ComponentState) then - Invalidate; - end - else - if AComponent = PopupMenu then - PopupMenu := nil - else - // Check for components linked to the header. - if Assigned(FHeader) then - begin - if AComponent = FHeader.Images then - FHeader.Images := nil - else - if AComponent = FHeader.PopupMenu then - FHeader.PopupMenu := nil; - end; - end; - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.OriginalWMNCPaint(DC: HDC); - -// Unfortunately, the painting for the non-client area in TControl is not always correct and does also not consider -// existing clipping regions, so it has been modified here to take this into account. - -const - InnerStyles: array[TBevelCut] of Integer = (0, BDR_SUNKENINNER, BDR_RAISEDINNER, 0); - OuterStyles: array[TBevelCut] of Integer = (0, BDR_SUNKENOUTER, BDR_RAISEDOUTER, 0); - EdgeStyles: array[TBevelKind] of Integer = (0, 0, BF_SOFT, BF_FLAT); - Ctl3DStyles: array[Boolean] of Integer = (BF_MONO, 0); - -var - RC, RW: TRect; - EdgeSize: Integer; - Size: TSize; - -begin - if (BevelKind <> bkNone) or (BorderWidth > 0) then - begin - RC := Rect(0, 0, Width, Height); - Size := GetBorderDimensions; - InflateRect(RC, Size.cx, Size.cy); - - RW := RC; - - if BevelKind <> bkNone then - begin - DrawEdge(DC, RC, InnerStyles[BevelInner] or OuterStyles[BevelOuter], Byte(BevelEdges) or EdgeStyles[BevelKind] or - Ctl3DStyles[Ctl3D]); - - EdgeSize := 0; - if BevelInner <> bvNone then - Inc(EdgeSize, BevelWidth); - if BevelOuter <> bvNone then - Inc(EdgeSize, BevelWidth); - with TWithSafeRect(RC) do - begin - if beLeft in BevelEdges then - Inc(Left, EdgeSize); - if beTop in BevelEdges then - Inc(Top, EdgeSize); - if beRight in BevelEdges then - Dec(Right, EdgeSize); - if beBottom in BevelEdges then - Dec(Bottom, EdgeSize); - end; - end; - - // Repaint only the part in the original clipping region and not yet drawn parts. - IntersectClipRect(DC, RC.Left, RC.Top, RC.Right, RC.Bottom); - - // Determine inner rectangle to exclude (RC corresponds then to the client area). - InflateRect(RC, -Integer(BorderWidth), -Integer(BorderWidth)); - - // Remove the inner rectangle. - ExcludeClipRect(DC, RC.Left, RC.Top, RC.Right, RC.Bottom); - - // Erase parts not drawn. - Brush.Color := FColors.BorderColor; - Winapi.Windows.FillRect(DC, RW, Brush.Handle); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.Paint; - -// Window paint routine. Used when the tree window needs to be updated. - -var - Window: TRect; - Target: TPoint; - Temp: Integer; - Options: TVTInternalPaintOptions; - RTLOffset: Integer; - -begin - - Options := [poBackground, poColumnColor, poDrawFocusRect, poDrawDropMark, poDrawSelection, poGridLines]; - if UseRightToLeftAlignment and FHeader.UseColumns then - RTLOffset := ComputeRTLOffset(True) - else - RTLOffset := 0; - - // The update rect has already been filled in WMPaint, as it is the window's update rect, which gets - // reset when BeginPaint is called (in the ancestor). - // The difference to the DC's clipbox is that it is also valid with internal paint operations used - // e.g. by the Explorer while dragging, but show window content while dragging is disabled. - if not IsRectEmpty(FUpdateRect) then - begin - Temp := Header.Columns.GetVisibleFixedWidth; - if Temp = 0 then - begin - Window := FUpdateRect; - Target := Window.TopLeft; - - // The clipping rectangle is given in client coordinates of the window. We have to convert it into - // a sliding window of the tree image. - OffsetRect(Window, FEffectiveOffsetX - RTLOffset, -FOffsetY); - PaintTree(Canvas, Window, Target, Options); - end - else - begin - // First part, fixed columns - Window := ClientRect; - Window.Right := Temp; - Target := Window.TopLeft; - - OffsetRect(Window, -RTLOffset, -FOffsetY); - PaintTree(Canvas, Window, Target, Options); - - // Second part, other columns - Window := GetClientRect; - - if Temp > Window.Right then - Exit; - - Window.Left := Temp; - Target := Window.TopLeft; - - OffsetRect(Window, FEffectiveOffsetX - RTLOffset, -FOffsetY); - PaintTree(Canvas, Window, Target, Options); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.PaintCheckImage(Canvas: TCanvas; const ImageInfo: TVTImageInfo; Selected: Boolean); - -var - ForegroundColor: COLORREF; - R: TRect; - Details, lSizeDetails: TThemedElementDetails; - lSize: TSize; - Theme: HTHEME; -begin - with ImageInfo do - begin - if (tsUseThemes in FStates) and (FCheckImageKind = ckSystemDefault) then - begin - Details.Element := teButton; - case Index of - // ctRadioButton - 1 : Details := StyleServices.GetElementDetails(tbRadioButtonUncheckedNormal); - 2 : Details := StyleServices.GetElementDetails(tbRadioButtonUncheckedHot); - 3 : Details := StyleServices.GetElementDetails(tbRadioButtonUncheckedPressed); - 4 : Details := StyleServices.GetElementDetails(tbRadioButtonUncheckedDisabled); - 5 : Details := StyleServices.GetElementDetails(tbRadioButtonCheckedNormal); - 6 : Details := StyleServices.GetElementDetails(tbRadioButtonCheckedHot); - 7 : Details := StyleServices.GetElementDetails(tbRadioButtonCheckedPressed); - 8 : Details := StyleServices.GetElementDetails(tbRadioButtonCheckedDisabled); - // ct(TriState)CheckBox - 9 : Details := StyleServices.GetElementDetails(tbCheckBoxUncheckedNormal); - 10 : Details := StyleServices.GetElementDetails(tbCheckBoxUncheckedHot); - 11 : Details := StyleServices.GetElementDetails(tbCheckBoxUncheckedPressed); - 12 : Details := StyleServices.GetElementDetails(tbCheckBoxUncheckedDisabled); - 13 : Details := StyleServices.GetElementDetails(tbCheckBoxCheckedNormal); - 14 : Details := StyleServices.GetElementDetails(tbCheckBoxCheckedHot); - 15 : Details := StyleServices.GetElementDetails(tbCheckBoxCheckedPressed); - 16 : Details := StyleServices.GetElementDetails(tbCheckBoxCheckedDisabled); - 17 : Details := StyleServices.GetElementDetails(tbCheckBoxMixedNormal); - 18 : Details := StyleServices.GetElementDetails(tbCheckBoxMixedHot); - 19 : Details := StyleServices.GetElementDetails(tbCheckBoxMixedPressed); - 20 : Details := StyleServices.GetElementDetails(tbCheckBoxMixedDisabled); - // ctButton - ckButtonNormal: Details := StyleServices.GetElementDetails(tbPushButtonNormal); - ckButtonHot: Details := StyleServices.GetElementDetails(tbPushButtonHot); - ckButtonPressed: Details := StyleServices.GetElementDetails(tbPushButtonPressed); - ckButtonDisabled: Details := StyleServices.GetElementDetails(tbPushButtonDisabled); - else - Details := StyleServices.GetElementDetails(tbButtonRoot); - end; - if StyleServices.IsSystemStyle {and not (Index in [ckButtonNormal..ckButtonDisabled])} then - begin - Theme := OpenThemeData(Handle, 'BUTTON'); - GetThemePartSize(Theme, Canvas.Handle, Details.Part, Details.State, nil, TS_TRUE, lSize); - if (Index in [ckButtonNormal..ckButtonDisabled]) then begin - lSizeDetails := StyleServices.GetElementDetails(tbCheckBoxCheckedNormal); // Size of dropdown button should be based on size of checkboxes - GetThemePartSize(Theme, Canvas.Handle, lSizeDetails.Part, lSizeDetails.State, nil, TS_TRUE, lSize); - // dropdown buttons should be slightly larger than checkboxes, see issue #887 - lSize.cx := Round(lSize.cx * 1.15); - lSize.cy := Round(lSize.cy * 1.1); - end; - R := Rect(XPos, YPos, XPos + lSize.cx, YPos + lSize.cy); - if (Index in [ckButtonNormal..ckButtonDisabled]) then - R.Offset(-1, 0); // Eliminate 1 pixel border around Windows themed button - DrawThemeBackground(Theme, Canvas.Handle, Details.Part, Details.State, R, nil); - CloseThemeData(Theme); - end - else - begin - if (Index in [ckButtonNormal..ckButtonDisabled]) or not StyleServices.GetElementSize(Canvas.Handle, Details, TElementSize.esActual, lSize{$IF CompilerVersion >= 34}, CurrentPPI{$IFEND}) then begin - // radio buttons fail in RAD Studio 10 Seattle and lower, fallback to checkbox images. See issue #615 - if not StyleServices.GetElementSize(Canvas.Handle, StyleServices.GetElementDetails(tbCheckBoxUncheckedNormal), TElementSize.esActual, lSize{$IF CompilerVersion >= 34}, CurrentPPI{$IFEND}) then - lSize := TSize.Create(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK)); - end;//if - R := Rect(XPos, YPos, XPos + lSize.cx, YPos + lSize.cy); - StyleServices.DrawElement(Canvas.Handle, Details, R {$IF CompilerVersion >= 34}, nil, FCurrentPPI{$IFEND}); - Canvas.Refresh; // Every time you give a Canvas.Handle away to some other code you can't control you have to call Canvas.Refresh afterwards because the Canvas object and the HDC can be out of sync. - end; - if (Index in [ckButtonNormal..ckButtonDisabled]) then begin - Canvas.Pen.Color := clGray; - // These constants have been determined by test using various themes and dpi-scalings - DrawArrow(Canvas, TScrollDirection.sdDown, Point(R.Left + Round(lSize.cx * 0.22), R.Top + Round(lSize.cy * 0.33)), Round(lSize.cx *0.28)); - end;//if - end - else - with FCheckImages do - begin - if Selected and not Ghosted then - begin - if Focused or (toPopupMode in FOptions.PaintOptions) then - ForegroundColor := ColorToRGB(FColors.FocusedSelectionColor) - else - ForegroundColor := ColorToRGB(FColors.UnfocusedSelectionColor); - end - else - ForegroundColor := GetRGBColor(BlendColor); - - ImageList_DrawEx(Handle, Index, Canvas.Handle, XPos, YPos, 0, 0, GetRGBColor(BkColor), ForegroundColor, - ILD_TRANSPARENT); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - - -procedure TBaseVirtualTree.PaintImage(var PaintInfo: TVTPaintInfo; ImageInfoIndex: TVTImageInfoIndex; DoOverlay: Boolean); -const - Style: array[TImageType] of Cardinal = (0, ILD_MASK); -var - ExtraStyle: Cardinal; - CutNode: Boolean; - PaintFocused: Boolean; - DrawEnabled: Boolean; - CustomOverlayDrawing: Boolean; // False if the built-in overloay drawing of TImageList should be used, True if custom drawing should take place. -begin - with PaintInfo do - begin - CutNode := (vsCutOrCopy in Node.States) and (tsCutPending in FStates); - PaintFocused := Focused or (toGhostedIfUnfocused in FOptions.PaintOptions); - - // Since the overlay image must be specified together with the image to draw - // it is meaningfull to retrieve it in advance. - if DoOverlay then - GetImageIndex(PaintInfo, ikOverlay, iiOverlay) - else - PaintInfo.ImageInfo[iiOverlay].Index := -1; - - DrawEnabled := not (vsDisabled in Node.States) and Enabled; - with ImageInfo[ImageInfoIndex] do - begin - if (vsSelected in Node.States) and not(Ghosted or CutNode) then - begin - if PaintFocused or (toPopupMode in FOptions.PaintOptions) then - Images.BlendColor := FColors.FocusedSelectionColor - else - Images.BlendColor := FColors.UnfocusedSelectionColor; - end - else - Images.BlendColor := Color; - - ExtraStyle := ILD_TRANSPARENT; - // If the user returned an index >= 15 then we cannot use the built-in overlay image drawing. - // Instead we do it manually. Also of the image list of the normal and the overlay icon is different, - // we can't use the built-in drawing. See issue #779. - if (ImageInfo[iiOverlay].Index > -1) then begin - CustomOverlayDrawing := (ImageInfo[iiOverlay].Index >= 15) or (ImageInfo[iiOverlay].Images <> ImageInfo[iiNormal].Images); - if not CustomOverlayDrawing then - ExtraStyle := ILD_TRANSPARENT or ILD_OVERLAYMASK and IndexToOverlayMask(ImageInfo[iiOverlay].Index + 1); - end - else - CustomOverlayDrawing := False; - - // Blend image if enabled and the tree has the focus (or ghosted images must be drawn also if unfocused) ... - if (toUseBlendedImages in FOptions.PaintOptions) and PaintFocused - // ... and the image is ghosted... - and (Ghosted or - // ... or it is not the check image and the node is selected (but selection is not for the entire row)... - ((vsSelected in Node.States) and - not (toFullRowSelect in FOptions.SelectionOptions) and - not (toGridExtensions in FOptions.MiscOptions)) or - // ... or the node must be shown in cut mode. - CutNode) then - ExtraStyle := ExtraStyle or ILD_BLEND50; - - if (vsSelected in Node.States) and not Ghosted then - Images.BlendColor := clDefault; - - DrawImage(Images, Index, Canvas, XPos, YPos, Style[Images.ImageType] or ExtraStyle, DrawEnabled); - - // Now, draw the overlay. This circumnavigates limitations in the overlay mask index (it has to be 4 bits in size, - // anything larger will be truncated by the ILD_OVERLAYMASK). - // However this will only be done if the overlay image index is > 15, to avoid breaking code that relies - // on overlay image indices (e.g. when using system image lists). - if CustomOverlayDrawing then begin - ExtraStyle := ExtraStyle and not ILD_BLEND50; // Fixes issue #551 - // Note: XPos and YPos are those of the normal images. - DrawImage(ImageInfo[iiOverlay].Images, ImageInfo[iiOverlay].Index, Canvas, XPos, YPos, - Style[ImageInfo[iiOverlay].Images.ImageType] or ExtraStyle, DrawEnabled); - end;//if - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.PaintNodeButton(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const R: TRect; - ButtonX, ButtonY: Integer; BidiMode: TBiDiMode); - -var - Bitmap: TBitmap; - XPos: Integer; - IsHot: Boolean; - IsSelected : boolean; - Theme: HTHEME; - Glyph: Integer; - State: Integer; - Pos: TRect; - -begin - IsHot := (FCurrentHotNode = Node) and FHotNodeButtonHit; - IsSelected := (vsSelected in Node.States); - - // Draw the node's plus/minus button according to the directionality. - if BidiMode = bdLeftToRight then - XPos := R.Left + ButtonX - else - XPos := R.Right - ButtonX - FPlusBM.Width; - - if (tsUseExplorerTheme in FStates) and not VclStyleEnabled then - begin - Glyph := IfThen(IsHot, TVP_HOTGLYPH, TVP_GLYPH); - State := IfThen(vsExpanded in Node.States, GLPS_OPENED, GLPS_CLOSED); - Pos := Rect(XPos, R.Top + ButtonY, XPos + FPlusBM.Width, R.Top + ButtonY + FPlusBM.Height); - Theme := OpenThemeData(Handle, 'TREEVIEW'); - DrawThemeBackground(Theme, Canvas.Handle, Glyph, State, Pos, nil); - CloseThemeData(Theme); - end - else - begin - if vsExpanded in Node.States then - begin - if IsHot then - begin - if IsSelected then - BitMap := FSelectedHotMinusBM - else - Bitmap := FHotMinusBM; - end - else - Bitmap := FMinusBM; - end - else - begin - if IsHot then - begin - if IsSelected then - BitMap := FSelectedHotPlusBM - else - Bitmap := FHotPlusBM; - end - else - Bitmap := FPlusBM; - end; - // Need to draw this masked. - Canvas.Draw(XPos, R.Top + ButtonY, Bitmap); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.PaintTreeLines(const PaintInfo: TVTPaintInfo; IndentSize: Integer; const LineImage: TLineImage); - -var - I: Integer; - XPos, - Offset: Integer; - NewStyles: TLineImage; - -begin - NewStyles := nil; - - with PaintInfo do - begin - if BidiMode = bdLeftToRight then - begin - XPos := CellRect.Left + PaintInfo.Offsets[ofsMargin]; - Offset := FIndent; - end - else - begin - Offset := -Integer(FIndent); - XPos := CellRect.Right - PaintInfo.Offsets[ofsMargin] + Offset; - end; - - case FLineMode of - lmBands: - if poGridLines in PaintInfo.PaintOptions then - begin - // Convert the line images in correct bands. - SetLength(NewStyles, Length(LineImage)); - for I := IndentSize - 1 downto 0 do - begin - if (vsExpanded in Node.States) and not (vsAllChildrenHidden in Node.States) then - NewStyles[I] := ltLeft - else - case LineImage[I] of - ltRight, - ltBottomRight, - ltTopDownRight, - ltTopRight: - NewStyles[I] := ltLeftBottom; - ltNone: - // Have to take over the image to the right of this one. A no line entry can never appear as - // last entry so I don't need an end check here. - if LineImage[I + 1] in [ltNone, ltTopRight] then - NewStyles[I] := NewStyles[I + 1] - else - NewStyles[I] := ltLeft; - ltTopDown: - // Have to check the image to the right of this one. A top down line can never appear as - // last entry so I don't need an end check here. - if LineImage[I + 1] in [ltNone, ltTopRight] then - NewStyles[I] := NewStyles[I + 1] - else - NewStyles[I] := ltLeft; - end; - end; - - PaintInfo.Canvas.Font.Color := FColors.GridLineColor; - for I := 0 to IndentSize - 1 do - begin - DoBeforeDrawLineImage(PaintInfo.Node, I + Ord(not (toShowRoot in TreeOptions.PaintOptions)), XPos); - DrawLineImage(PaintInfo, XPos, CellRect.Top, NodeHeight[Node] - 1, VAlign - 1, NewStyles[I], - BidiMode <> bdLeftToRight); - Inc(XPos, Offset); - end; - end; - else // lmNormal - PaintInfo.Canvas.Font.Color := FColors.TreeLineColor; - for I := 0 to IndentSize - 1 do - begin - DoBeforeDrawLineImage(PaintInfo.Node, I + Ord(not (toShowRoot in TreeOptions.PaintOptions)), XPos); - DrawLineImage(PaintInfo, XPos, CellRect.Top, NodeHeight[Node], VAlign - 1, LineImage[I], - BidiMode <> bdLeftToRight); - Inc(XPos, Offset); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.PaintSelectionRectangle(Target: TCanvas; WindowOrgX: Integer; const SelectionRect: TRect; - TargetRect: TRect); - -// Helper routine to draw a selection rectangle in the mode determined by DrawSelectionMode. - -var - BlendRect: TRect; - TextColorBackup, - BackColorBackup: COLORREF; // used to restore forground and background colors when drawing a selection rectangle - -begin - if ((FDrawSelectionMode = smDottedRectangle) and not (tsUseThemes in FStates)) then - begin - // Classical selection rectangle using dotted borderlines. - TextColorBackup := GetTextColor(Target.Handle); - SetTextColor(Target.Handle, $FFFFFF); - BackColorBackup := GetBkColor(Target.Handle); - SetBkColor(Target.Handle, 0); - Target.DrawFocusRect(SelectionRect); - SetTextColor(Target.Handle, TextColorBackup); - SetBkColor(Target.Handle, BackColorBackup); - end - else - begin - // Modern alpha blended style. - OffsetRect(TargetRect, WindowOrgX, 0); - if IntersectRect(BlendRect, OrderRect(SelectionRect), TargetRect) then - begin - OffsetRect(BlendRect, -WindowOrgX, 0); - AlphaBlend(0, Target.Handle, BlendRect, Point(0, 0), bmConstantAlphaAndColor, FSelectionBlendFactor, - ColorToRGB(FColors.SelectionRectangleBlendColor)); - - Target.Brush.Color := FColors.SelectionRectangleBorderColor; - Target.FrameRect(SelectionRect); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.PanningWindowProc(var Message: TMessage); - -var - PS: TPaintStruct; - Canvas: TCanvas; - -begin - if Message.Msg = WM_PAINT then - begin - BeginPaint(FPanningWindow, PS); - Canvas := TCanvas.Create; - Canvas.Handle := PS.hdc; - try - Canvas.Draw(0, 0, FPanningImage); - finally - Canvas.Handle := 0; - Canvas.Free; - EndPaint(FPanningWindow, PS); - end; - Message.Result := 0; - end - else - with Message do - Result := DefWindowProc(FPanningWindow, Msg, wParam, lParam); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer); - -// This method is called immediately before a cell's content is drawn und is responsible to paint selection colors etc. - -var - TextColorBackup, - BackColorBackup: COLORREF; - FocusRect, - InnerRect: TRect; - RowRect: TRect; - Theme: HTHEME; -const - TREIS_HOTSELECTED = 6; - - //--------------- local functions ------------------------------------------- - - procedure AlphaBlendSelection(Color: TColor); - - var - R: TRect; - - begin - // Take into account any window offset and size limitations in the target bitmap, as this is only as large - // as necessary and might not cover the whole node. For normal painting this does not matter (because of - // clipping) but for the MMX code there is no such check and it will crash badly when bitmap boundaries are - // crossed. - R := InnerRect; - OffsetRect(R, -WindowOrgX, 0); - if R.Left < 0 then - R.Left := 0; - if R.Right > MaxWidth then - R.Right := MaxWidth; - AlphaBlend(0, PaintInfo.Canvas.Handle, R, Point(0, 0), bmConstantAlphaAndColor, - FSelectionBlendFactor, ColorToRGB(Color)); - end; - - //--------------------------------------------------------------------------- - - procedure DrawBackground(State: Integer); - begin - // if the full row selection is disabled or toGridExtensions is in the MiscOptions, draw the selection - // into the InnerRect, otherwise into the RowRect - if not (toFullRowSelect in FOptions.SelectionOptions) or (toGridExtensions in FOptions.MiscOptions) then - DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, TVP_TREEITEM, State, InnerRect, nil) - else - DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, TVP_TREEITEM, State, RowRect, nil); - end; - - procedure DrawThemedFocusRect(State: Integer); - var - Theme: HTHEME; - begin - Theme := OpenThemeData(Application.ActiveFormHandle, 'Explorer::ItemsView'); - if not (toFullRowSelect in FOptions.SelectionOptions) or (toGridExtensions in FOptions.MiscOptions) then - DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, LVP_LISTDETAIL, State, InnerRect, nil) - else - DrawThemeBackground(Theme, PaintInfo.Canvas.Handle, LVP_LISTDETAIL, State, RowRect, nil); - CloseThemeData(Theme); - end; - - //--------------- end local functions --------------------------------------- - -begin - if tsUseExplorerTheme in FStates then - begin - Theme := OpenThemeData(Application.ActiveFormHandle, 'Explorer::TreeView'); - RowRect := Rect(0, PaintInfo.CellRect.Top, FRangeX, PaintInfo.CellRect.Bottom); - if (Header.Columns.Count = 0) and (toFullRowSelect in TreeOptions.SelectionOptions) then - RowRect.Right := Max(ClientWidth, RowRect.Right); - if toShowVertGridLines in FOptions.PaintOptions then - Dec(RowRect.Right); - end; - - with PaintInfo, Canvas do - begin - // Fill cell background if its color differs from tree background. - with FHeader.Columns do - if poColumnColor in PaintOptions then - begin - Brush.Color := Items[Column].GetEffectiveColor; - FillRect(CellRect); - end; - - // Let the application customize the cell background and the content rectangle. - DoBeforeCellPaint(Canvas, Node, Column, cpmPaint, CellRect, ContentRect); - - InnerRect := ContentRect; - - // The selection rectangle depends on alignment. - if not (toGridExtensions in FOptions.MiscOptions) then - begin - case Alignment of - taLeftJustify: - with TWithSafeRect(InnerRect) do - if Left + NodeWidth < Right then - Right := Left + NodeWidth; - taCenter: - with TWithSafeRect(InnerRect) do - if (Right - Left) > NodeWidth then - begin - Left := (Left + Right - NodeWidth) div 2; - Right := Left + NodeWidth; - end; - taRightJustify: - with TWithSafeRect(InnerRect) do - if (Right - Left) > NodeWidth then - Left := Right - NodeWidth; - end; - end; - - if (Column = FFocusedColumn) or (toFullRowSelect in FOptions.SelectionOptions) then - begin - // Fill the selection rectangle. - if poDrawSelection in PaintOptions then - begin - if Node = FDropTargetNode then - begin - if (FLastDropMode = dmOnNode) or (vsSelected in Node.States) then - begin - Brush.Color := FColors.DropTargetColor; - Pen.Color := FColors.DropTargetBorderColor; - - if (toGridExtensions in FOptions.MiscOptions) or - (toFullRowSelect in FOptions.SelectionOptions) then - InnerRect := CellRect; - if not IsRectEmpty(InnerRect) then - if tsUseExplorerTheme in FStates then - DrawBackground(TREIS_SELECTED) - else - if (toUseBlendedSelection in FOptions.PaintOptions) then - AlphaBlendSelection(Brush.Color) - else - with TWithSafeRect(InnerRect) do - RoundRect(Left, Top, Right, Bottom, FSelectionCurveRadius, FSelectionCurveRadius); - end - else - begin - Brush.Style := bsClear; - end; - end - else - if vsSelected in Node.States then - begin - if Focused or (toPopupMode in FOptions.PaintOptions) then - begin - Brush.Color := FColors.FocusedSelectionColor; - Pen.Color := FColors.FocusedSelectionBorderColor; - end - else - begin - Brush.Color := FColors.UnfocusedSelectionColor; - Pen.Color := FColors.UnfocusedSelectionBorderColor; - end; - if (toGridExtensions in FOptions.MiscOptions) or (toFullRowSelect in FOptions.SelectionOptions) then - InnerRect := CellRect; - if not IsRectEmpty(InnerRect) then - if tsUseExplorerTheme in FStates then - begin - // If the node is also hot, its background will be drawn later. - if not (toHotTrack in FOptions.PaintOptions) or (Node <> FCurrentHotNode) or - ((Column <> FCurrentHotColumn) and not (toFullRowSelect in FOptions.SelectionOptions)) then - DrawBackground(IfThen(Self.Focused, TREIS_SELECTED, TREIS_SELECTEDNOTFOCUS)); - end - else - if (toUseBlendedSelection in FOptions.PaintOptions) then - AlphaBlendSelection(Brush.Color) - else - with TWithSafeRect(InnerRect) do - RoundRect(Left, Top, Right, Bottom, FSelectionCurveRadius, FSelectionCurveRadius); - end; - end; - end; - - if (tsUseExplorerTheme in FStates) and (toHotTrack in FOptions.PaintOptions) and (Node = FCurrentHotNode) and - ((Column = FCurrentHotColumn) or (toFullRowSelect in FOptions.SelectionOptions)) then - DrawBackground(IfThen((vsSelected in Node.States) and not (toAlwaysHideSelection in FOptions.PaintOptions), - TREIS_HOTSELECTED, TREIS_HOT)); - - if (Column = FFocusedColumn) or (toFullRowSelect in FOptions.SelectionOptions) then - begin - // draw focus rect - if (poDrawFocusRect in PaintOptions) and - (Focused or (toPopupMode in FOptions.PaintOptions)) and (FFocusedNode = Node) and - ( (Column = FFocusedColumn) or - ((not (toExtendedFocus in FOptions.SelectionOptions) or IsWinVistaOrAbove) and - (toFullRowSelect in FOptions.SelectionOptions) and - (tsUseExplorerTheme in FStates) ) ) then - begin - TextColorBackup := GetTextColor(Handle); - SetTextColor(Handle, $FFFFFF); - BackColorBackup := GetBkColor(Handle); - SetBkColor(Handle, 0); - - if not (toExtendedFocus in FOptions.SelectionOptions) and (toFullRowSelect in FOptions.SelectionOptions) and - (tsUseExplorerTheme in FStates) then - FocusRect := RowRect - else - if toGridExtensions in FOptions.MiscOptions then - FocusRect := CellRect - else - FocusRect := InnerRect; - - if tsUseExplorerTheme in FStates then - InflateRect(FocusRect, -1, -1); - - if (tsUseExplorerTheme in FStates) and IsWinVistaOrAbove then - begin - //Draw focused unselected style like Windows 7 Explorer - if not (vsSelected in Node.States) then - DrawThemedFocusRect(LIS_NORMAL) - else - DrawBackground(TREIS_HOTSELECTED); - end - else - Winapi.Windows.DrawFocusRect(Handle, FocusRect); - SetTextColor(Handle, TextColorBackup); - SetBkColor(Handle, BackColorBackup); - end; - end; - end; - - if tsUseExplorerTheme in FStates then - CloseThemeData(Theme); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ReadChunk(Stream: TStream; Version: Integer; Node: PVirtualNode; ChunkType, - ChunkSize: Integer): Boolean; - -// Called while loading a tree structure, Node is already valid (allocated) at this point. -// The function handles the base and user chunks, any other chunk is marked as being unknown (result becomes False) -// and skipped. descendants may handle them by overriding this method. -// Returns True if the chunk could be handled, otherwise False. -type - TAdvancedVersion2Identifier = packed record - ChildCount, - NodeHeight: Cardinal; - States: Word; - Align: Byte; - CheckState: TCheckState; - CheckType: TCheckType; - Reserved: Cardinal; - end; - -var - IdBody: TAdvancedVersion2Identifier; - ChunkBody: TBaseChunkBody; - Run: PVirtualNode; - LastPosition: Integer; - -begin - case ChunkType of - BaseChunk: - begin - // Load base chunk's body (chunk header has already been consumed). - case Version of - 1: - begin - with ChunkBody do - begin - // In version prior to 2 there was a smaller chunk body. Hence we have to read it entry by entry now. - Stream.Read(ChildCount, SizeOf(ChildCount)); - Stream.Read(NodeHeight, SizeOf(NodeHeight)); - // TVirtualNodeStates was a byte sized type in version 1. - States := []; - Stream.Read(States, SizeOf(Byte)); - // vsVisible is now in the place where vsSelected was before, but every node was visible in the old version - // so we need to fix this too. - if vsVisible in States then - //sync path note: prior version stream reading, ignored for syncing - Include(States, vsSelected) - else - Include(States, vsVisible); - Stream.Read(Align, SizeOf(Align)); - Stream.Read(CheckState, SizeOf(CheckState)); - Stream.Read(CheckType, SizeOf(CheckType)); - end; - end; - 2: - begin - ZeroMemory(@IdBody, SizeOf(IdBody)); - Stream.Read(IdBody, SizeOf(IdBody)); - // If Align is greater than zero, we have a stream prior to VT version 6.2 - if IdBody.Align > 0 then - with ChunkBody do - begin - ChildCount := IdBody.ChildCount; - NodeHeight := IdBody.NodeHeight; - States := []; - Move(IdBody.States, States, SizeOf(IdBody.States)); - CheckState := IdBody.CheckState; - CheckType := IdBody.CheckType; - Reserved := IdBody.Reserved; - end - else - begin - // Stream is compatible with current size of TBaseChunkBody - Stream.Position := Stream.Position - SizeOf(IdBody); - Stream.Read(ChunkBody, SizeOf(ChunkBody)); - end; - end; - 3: - Stream.Read(ChunkBody, SizeOf(ChunkBody)); - end; - - with Node^ do - begin - // Set states first, in case the node is invisible. - States := ChunkBody.States; - NodeHeight := ChunkBody.NodeHeight; - TotalHeight := NodeHeight; - Align := ChunkBody.Align; - CheckState := ChunkBody.CheckState; - CheckType := ChunkBody.CheckType; - ChildCount := ChunkBody.ChildCount; - - // Create and read child nodes. - while ChunkBody.ChildCount > 0 do - begin - Run := MakeNewNode; - - Run.PrevSibling := Node.LastChild; - if Assigned(Run.PrevSibling) then - Run.Index := Run.PrevSibling.Index + 1; - if Assigned(Node.LastChild) then - Node.LastChild.NextSibling := Run - else - Node.FirstChild := Run; - Node.LastChild := Run; - Run.Parent := Node; - - ReadNode(Stream, Version, Run); - Dec(ChunkBody.ChildCount); - end; - end; - Result := True; - end; - UserChunk: - if ChunkSize > 0 then - begin - // need to know whether the data was read - LastPosition := Stream.Position; - DoLoadUserData(Node, Stream); - // compare stream position to learn whether the data was read - Result := Stream.Position > LastPosition; - // Improve stability by advancing the stream to the chunk's real end if - // the application did not read what has been written. - if not Result or (Stream.Position <> (LastPosition + ChunkSize)) then - Stream.Position := LastPosition + ChunkSize; - end - else - Result := True; - else - // unknown chunk, skip it - Stream.Position := Stream.Position + ChunkSize; - Result := False; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ReadNode(Stream: TStream; Version: Integer; Node: PVirtualNode); - -// Reads the anchor chunk of each node and initiates reading the sub chunks for this node - -var - Header: TChunkHeader; - EndPosition: Integer; - -begin - with Stream do - begin - // Read anchor chunk of the node. - Stream.Read(Header, SizeOf(Header)); - if Header.ChunkType = NodeChunk then - begin - EndPosition := Stream.Position + Header.ChunkSize; - // Read all subchunks until the indicated chunk end position is reached in the stream. - while Position < EndPosition do - begin - // Read new chunk header. - Stream.Read(Header, SizeOf(Header)); - ReadChunk(Stream, Version, Node, Header.ChunkType, Header.ChunkSize); - end; - // If the last chunk does not end at the given end position then there is something wrong. - if Position <> EndPosition then - ShowError(SCorruptStream2, hcTFCorruptStream2); - end - else - ShowError(SCorruptStream1, hcTFCorruptStream1); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.RedirectFontChangeEvent(Canvas: TCanvas); - -begin - if @Canvas.Font.OnChange <> @FOldFontChange then - begin - FOldFontChange := Canvas.Font.OnChange; - Canvas.Font.OnChange := FontChanged; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.RemoveFromSelection(Node: PVirtualNode); - -var - Index: Integer; - -begin - if not FSelectionLocked then - begin - Assert(Assigned(Node), 'Node must not be nil!'); - Assert(GetCurrentThreadId = MainThreadId, Self.Classname + '.RemoveFromSelection() must only be called from UI thread.'); - if vsSelected in Node.States then - begin - Assert(FSelectionCount > 0, 'if one node has set the vsSelected flag, SelectionCount must be >0.'); - //sync path note: deselect when a ctrl click removes a selection - Exclude(Node.States, vsSelected); - if SyncCheckstateWithSelection[Node] then - Node.CheckState := csUncheckedNormal; // Avoid using SetCheckState() as it handles toSyncCheckboxesWithSelection as well. - - if FindNodeInSelection(Node, Index, -1, -1) and (Index < FSelectionCount - 1) then - Move(FSelection[Index + 1], FSelection[Index], (FSelectionCount - Index - 1) * SizeOf(Pointer)); - if FSelectionCount > 0 then - Dec(FSelectionCount); - SetLength(FSelection, FSelectionCount); - - if FSelectionCount = 0 then - ResetRangeAnchor; - - if FSelectionCount <= 1 then - UpdateNextNodeToSelect(Node); - - DoRemoveFromSelection(Node); - InvalidateNode(Node); - Change(Node); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateNextNodeToSelect(Node: PVirtualNode); - -// save a potential node to select after the currently selected node will be deleted. -// This will make the VT to behave more like the Win32 TreeView, which always selecta a new node if the currently -// selected one gets deleted. - -begin - if not (toAlwaysSelectNode in TreeOptions.SelectionOptions) then - Exit; - if GetNextSibling(Node) <> nil then - FNextNodeToSelect := GetNextSibling(Node) - else if GetPreviousSibling(Node) <> nil then - FNextNodeToSelect := GetPreviousSibling(Node) - else if Node.Parent <> FRoot then - FNextNodeToSelect := Node.Parent - else - FNextNodeToSelect := nil; -end;//if Assigned(Node); - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.RenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium; - ForClipboard: Boolean): HResult; - -// Returns a memory expression of all currently selected nodes in the Medium structure. -// Note: The memory requirement of this method might be very high. This depends however on the requested storage format. -// For HGlobal (a global memory block) we need to render first all nodes to local memory and copy this then to -// the global memory in Medium. This is necessary because we have first to determine how much -// memory is needed before we can allocate it. Hence for a short moment we need twice the space as used by the -// nodes alone (plus the amount the nodes need in the tree anyway)! -// With IStream this does not happen. We directly stream out the nodes and pass the constructed stream along. - - //--------------- local function -------------------------------------------- - - procedure WriteNodes(Stream: TStream); - - var - Selection: TNodeArray; - I: Integer; - - begin - if ForClipboard then - Selection := GetSortedCutCopySet(True) - else - Selection := GetSortedSelection(True); - for I := 0 to High(Selection) do - WriteNode(Stream, Selection[I]); - end; - - //--------------- end local function ---------------------------------------- - -var - Data: PCardinal; - ResPointer: Pointer; - ResSize: Integer; - OLEStream: IStream; - VCLStream: TStream; - -begin - ZeroMemory (@Medium, SizeOf(Medium)); - - // We can render the native clipboard format in two different storage media. - if (FormatEtcIn.cfFormat = CF_VIRTUALTREE) and (FormatEtcIn.tymed and (TYMED_HGLOBAL or TYMED_ISTREAM) <> 0) then - begin - VCLStream := nil; - try - Medium.unkForRelease := nil; - // Return data in one of the supported storage formats, prefer IStream. - if FormatEtcIn.tymed and TYMED_ISTREAM <> 0 then - begin - // Create an IStream on a memory handle (here it is 0 which indicates to implicitely allocated a handle). - // Do not use TStreamAdapter as it is not compatible with OLE (when flushing the clipboard OLE wants the HGlobal - // back which is not supported by TStreamAdapater). - CreateStreamOnHGlobal(0, True, OLEStream); - VCLStream := TOLEStream.Create(OLEStream); - WriteNodes(VCLStream); - // Rewind stream. - VCLStream.Position := 0; - Medium.tymed := TYMED_ISTREAM; - IUnknown(Medium.stm) := OLEStream; - Result := S_OK; - end - else - begin - VCLStream := TMemoryStream.Create; - WriteNodes(VCLStream); - ResPointer := TMemoryStream(VCLStream).Memory; - ResSize := VCLStream.Position; - - // Allocate memory to hold the string. - if ResSize > 0 then - begin - Medium.hGlobal := GlobalAlloc(GHND or GMEM_SHARE, ResSize + SizeOf(Cardinal)); - Data := GlobalLock(Medium.hGlobal); - // Store the size of the data too, for easy retrival. - Data^ := ResSize; - Inc(Data); - Move(ResPointer^, Data^, ResSize); - GlobalUnlock(Medium.hGlobal); - Medium.tymed := TYMED_HGLOBAL; - - Result := S_OK; - end - else - Result := E_FAIL; - end; - finally - // We can free the VCL stream here since it was either a pure memory stream or only a wrapper around - // the OLEStream which exists independently. - VCLStream.Free; - end; - end - else // Ask application descendants to render self defined formats. - Result := DoRenderOLEData(FormatEtcIn, Medium, ForClipboard); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ResetRangeAnchor; - -// Called when there is no selected node anymore and the selection range anchor needs a new value. - -begin - FRangeAnchor := FFocusedNode; - FLastSelectionLevel := -1; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.RestoreFontChangeEvent(Canvas: TCanvas); - -begin - Canvas.Font.OnChange := FOldFontChange; - FOldFontChange := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SelectNodes(StartNode, EndNode: PVirtualNode; AddOnly: Boolean); - -// Selects a range of nodes and unselects all other eventually selected nodes which are not in this range if -// AddOnly is False. -// EndNode must be visible while StartNode does not necessarily as in the case where the last focused node is the start -// node but it is a child of a node which has been collapsed previously. In this case the first visible parent node -// is used as start node. StartNode can be nil in which case the very first node in the tree is used. - -var - NodeFrom, - NodeTo, - LastAnchor: PVirtualNode; - Index: Integer; - -begin - Assert(Assigned(EndNode), 'EndNode must not be nil!'); - if not FSelectionLocked then - begin - ClearTempCache; - if StartNode = nil then - StartNode := GetFirstVisibleNoInit(nil, True) - else - if not FullyVisible[StartNode] then - begin - StartNode := GetPreviousVisible(StartNode, True); - if StartNode = nil then - StartNode := GetFirstVisibleNoInit(nil, True); - end; - - if CompareNodePositions(StartNode, EndNode, True) < 0 then - begin - NodeFrom := StartNode; - NodeTo := EndNode; - end - else - begin - NodeFrom := EndNode; - NodeTo := StartNode; - end; - - // The range anchor will be reset by the following call. - LastAnchor := FRangeAnchor; - if not AddOnly then - InternalClearSelection; - - while NodeFrom <> NodeTo do - begin - InternalCacheNode(NodeFrom); - NodeFrom := GetNextVisible(NodeFrom, True); - end; - // select last node too - InternalCacheNode(NodeFrom); - // now add them all in "one" step - AddToSelection(FTempNodeCache, FTempNodeCount); - ClearTempCache; - if Assigned(LastAnchor) and FindNodeInSelection(LastAnchor, Index, -1, -1) then - FRangeAnchor := LastAnchor; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SetFocusedNodeAndColumn(Node: PVirtualNode; Column: TColumnIndex); - -var - OldColumn: TColumnIndex; - WasDifferent: Boolean; - -begin - if not FHeader.AllowFocus(Column) then - Column := FFocusedColumn; - - WasDifferent := (Node <> FFocusedNode) or (Column <> FFocusedColumn); - - OldColumn := FFocusedColumn; - FFocusedColumn := Column; - - DoFocusNode(Node, True); - - // Check if the change was accepted. - if FFocusedNode = Node then - begin - CancelEditNode; - if WasDifferent then - DoFocusChange(FFocusedNode, FFocusedColumn); - end - else - // If the user did not accept the new cell to focus then set also the focused column back - // to its original state. - FFocusedColumn := OldColumn; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SkipNode(Stream: TStream); - -// Skips the data for the next node in the given stream (including the child nodes). - -var - Header: TChunkHeader; - -begin - with Stream do - begin - // read achor chunk of the node - Stream.Read(Header, SizeOf(Header)); - if Header.ChunkType = NodeChunk then - Stream.Position := Stream.Position + Header.ChunkSize - else - ShowError(SCorruptStream1, hcTFCorruptStream1); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -var - PanningWindowClass: TWndClass = ( - style: 0; - lpfnWndProc: @DefWindowProc; - cbClsExtra: 0; - cbWndExtra: 0; - hInstance: 0; - hIcon: 0; - hCursor: 0; - hbrBackground: 0; - lpszMenuName: nil; - lpszClassName: 'VTPanningWindow' - ); - -procedure TBaseVirtualTree.StartWheelPanning(Position: TPoint); - -// Called when wheel panning should start. A little helper window is created to indicate the reference position, -// which determines in which direction and how far wheel panning/scrolling will happen. - - //--------------- local function -------------------------------------------- - - function CreateClipRegion: HRGN; - - // In order to avoid doing all the transparent drawing ourselves we use a - // window region for the wheel window. - // Since we only work on a very small image (32x32 pixels) this is acceptable. - - var - Start, X, Y: Integer; - Temp: HRGN; - - begin - Assert(not FPanningImage.Empty, 'Invalid wheel panning image.'); - - // Create an initial region on which we operate. - Result := CreateRectRgn(0, 0, 0, 0); - with FPanningImage, Canvas do - begin - for Y := 0 to Height - 1 do - begin - Start := -1; - for X := 0 to Width - 1 do - begin - // Start a new span if we found a non-transparent pixel and no span is currently started. - if (Start = -1) and (Pixels[X, Y] <> clFuchsia) then - Start := X - else - if (Start > -1) and (Pixels[X, Y] = clFuchsia) then - begin - // A non-transparent span is finished. Add it to the result region. - Temp := CreateRectRgn(Start, Y, X, Y + 1); - CombineRgn(Result, Result, Temp, RGN_OR); - DeleteObject(Temp); - Start := -1; - end; - end; - // If there is an open span then add this also to the result region. - if Start > -1 then - begin - Temp := CreateRectRgn(Start, Y, Width, Y + 1); - CombineRgn(Result, Result, Temp, RGN_OR); - DeleteObject(Temp); - end; - end; - end; - // The resulting region is used as window region so we must not delete it. - // Windows will own it after the assignment below. - end; - - //--------------- end local function ---------------------------------------- - -var - TempClass: TWndClass; - ClassRegistered: Boolean; - ImageName: string; - Pt: TPoint; - -begin - // Set both panning and scrolling flag. One will be removed shortly depending on whether the middle mouse button is - // released before the mouse is moved or vice versa. The first case is referred to as wheel scrolling while the - // latter is called wheel panning. - StopTimer(ScrollTimer); - DoStateChange([tsWheelPanning, tsWheelScrolling]); - - // Register the helper window class. - PanningWindowClass.hInstance := HInstance; - ClassRegistered := GetClassInfo(HInstance, PanningWindowClass.lpszClassName, TempClass); - if not ClassRegistered or (TempClass.lpfnWndProc <> @DefWindowProc) then - begin - if ClassRegistered then - Winapi.Windows.UnregisterClass(PanningWindowClass.lpszClassName, HInstance); - Winapi.Windows.RegisterClass(PanningWindowClass); - end; - // Create the helper window and show it at the given position without activating it. - Pt := ClientToScreen(Position); - FPanningWindow := CreateWindowEx(WS_EX_TOOLWINDOW, PanningWindowClass.lpszClassName, nil, WS_POPUP, Pt.X - 16, Pt.Y - 16, - 32, 32, Handle, 0, HInstance, nil); - - FPanningImage := TBitmap.Create; - if Integer(FRangeX) > ClientWidth then - begin - if Integer(FRangeY) > ClientHeight then - ImageName := 'VT_MOVEALL' - else - ImageName := 'VT_MOVEEW'; - end - else - ImageName := 'VT_MOVENS'; - FPanningImage.LoadFromResourceName(HInstance, ImageName); - SetWindowRgn(FPanningWindow, CreateClipRegion, False); - - {$ifdef CPUX64} - SetWindowLongPtr(FPanningWindow, GWLP_WNDPROC, LONG_PTR(System.Classes.MakeObjectInstance(PanningWindowProc))); - {$else} - SetWindowLong(FPanningWindow, GWL_WNDPROC, NativeInt(System.Classes.MakeObjectInstance(PanningWindowProc))); - {$endif CPUX64} - ShowWindow(FPanningWindow, SW_SHOWNOACTIVATE); - - // Setup the panscroll timer and capture all mouse input. - TrySetFocus(); - SetCapture(Handle); - SetTimer(Handle, ScrollTimer, 20, nil); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.StopWheelPanning; - -// Stops panning if currently active and destroys the helper window. - -var - Instance: Pointer; - -begin - if [tsWheelPanning, tsWheelScrolling] * FStates <> [] then - begin - // Release the mouse capture and stop the panscroll timer. - StopTimer(ScrollTimer); - ReleaseCapture; - DoStateChange([], [tsWheelPanning, tsWheelScrolling]); - - // Destroy the helper window. - {$ifdef CPUX64} - Instance := Pointer(GetWindowLongPtr(FPanningWindow, GWLP_WNDPROC)); - {$else} - Instance := Pointer(GetWindowLong(FPanningWindow, GWL_WNDPROC)); - {$endif CPUX64} - DestroyWindow(FPanningWindow); - if Instance <> @DefWindowProc then - System.Classes.FreeObjectInstance(Instance); - FPanningWindow := 0; - FPanningImage.Free; - FPanningImage := nil; - DeleteObject(FPanningCursor); - FPanningCursor := 0; - Winapi.Windows.SetCursor(Screen.Cursors[Cursor]); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.StructureChange(Node: PVirtualNode; Reason: TChangeReason); - -begin - AdviseChangeEvent(True, Node, Reason); - - if FUpdateCount = 0 then - begin - if (FChangeDelay > 0) and HandleAllocated and not (tsSynchMode in FStates) then - SetTimer(Handle, StructureChangeTimer, FChangeDelay, nil) - else - DoStructureChange(Node, Reason); - end; -end; - -function TBaseVirtualTree.StyleServices(AControl: TControl): TCustomStyleServices; -begin - if AControl = nil then - AControl := Self; - Result := VTStyleServices(AControl); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.SuggestDropEffect(Source: TObject; Shift: TShiftState; Pt: TPoint; - AllowedEffects: Integer): Integer; - -// determines the drop action to take if the drag'n drop operation ends on this tree -// Note: Source can be any Delphi object not just a virtual tree - -begin - Result := AllowedEffects; - - // prefer MOVE if source and target are the same control, otherwise whatever is allowed as initial value - if Assigned(Source) and (Source = Self) then - if (AllowedEffects and DROPEFFECT_MOVE) <> 0 then - Result := DROPEFFECT_MOVE - else // no change - else - // drag between different applicatons - if (AllowedEffects and DROPEFFECT_COPY) <> 0 then - Result := DROPEFFECT_COPY; - - // consider modifier keys and what is allowed at the moment, if none of the following conditions apply then - // the initial value just set is used - if ssCtrl in Shift then - begin - // copy or link - if ssShift in Shift then - begin - // link - if (AllowedEffects and DROPEFFECT_LINK) <> 0 then - Result := DROPEFFECT_LINK; - end - else - begin - // copy - if (AllowedEffects and DROPEFFECT_COPY) <> 0 then - Result := DROPEFFECT_COPY; - end; - end - else - begin - // move, link or default - if ssShift in Shift then - begin - // move - if (AllowedEffects and DROPEFFECT_MOVE) <> 0 then - Result := DROPEFFECT_MOVE; - end - else - begin - // link or default - if ssAlt in Shift then - begin - // link - if (AllowedEffects and DROPEFFECT_LINK) <> 0 then - Result := DROPEFFECT_LINK; - end; - // else default - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ToggleSelection(StartNode, EndNode: PVirtualNode); - -// Switchs the selection state of a range of nodes. -// Note: This method is specifically designed to help selecting ranges with the keyboard and considers therefore -// the range anchor. - -var - NodeFrom, - NodeTo: PVirtualNode; - NewSize: Integer; - Position: Integer; - -begin - if not FSelectionLocked then - begin - Assert(Assigned(EndNode), 'EndNode must not be nil!'); - if StartNode = nil then - StartNode := FRoot.FirstChild - else - if not FullyVisible[StartNode] then - StartNode := GetPreviousVisible(StartNode, True); - - Position := CompareNodePositions(StartNode, EndNode); - // nothing to do if start and end node are the same - if Position <> 0 then - begin - if Position < 0 then - begin - NodeFrom := StartNode; - NodeTo := EndNode; - end - else - begin - NodeFrom := EndNode; - NodeTo := StartNode; - end; - - ClearTempCache; - - // 1) toggle the start node if it is before the range anchor - if CompareNodePositions(NodeFrom, FRangeAnchor) < 0 then - if not (vsSelected in NodeFrom.States) then - InternalCacheNode(NodeFrom) - else - InternalRemoveFromSelection(NodeFrom); - - // 2) toggle all nodes within the range - NodeFrom := GetNextVisible(NodeFrom, True); - while NodeFrom <> NodeTo do - begin - if not (vsSelected in NodeFrom.States) then - InternalCacheNode(NodeFrom) - else - InternalRemoveFromSelection(NodeFrom); - NodeFrom := GetNextVisible(NodeFrom, True); - end; - - // 3) toggle end node if it is after the range anchor - if CompareNodePositions(NodeFrom, FRangeAnchor) > 0 then - if not (vsSelected in NodeFrom.States) then - InternalCacheNode(NodeFrom) - else - InternalRemoveFromSelection(NodeFrom); - - // Do some housekeeping if there was a change. - NewSize := PackArray(FSelection, FSelectionCount); - if NewSize > -1 then - begin - FSelectionCount := NewSize; - SetLength(FSelection, FSelectionCount); - end; - // If the range went over the anchor then we need to reselect it. - if not (vsSelected in FRangeAnchor.States) then - InternalCacheNode(FRangeAnchor); - if FTempNodeCount > 0 then - AddToSelection(FTempNodeCache, FTempNodeCount); - ClearTempCache; - end; - end; -end; - -procedure TBaseVirtualTree.TrySetFocus(); -begin - if Visible and CanFocus then - begin - try - Self.SetFocus(); - except - on EInvalidOperation do - Exit; - end; - end;//if -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UnselectNodes(StartNode, EndNode: PVirtualNode); - -// Deselects a range of nodes. -// EndNode must be visible while StartNode must not as in the case where the last focused node is the start node -// but it is a child of a node which has been collapsed previously. In this case the first visible parent node -// is used as start node. StartNode can be nil in which case the very first node in the tree is used. - -var - NodeFrom, - NodeTo: PVirtualNode; - NewSize: Integer; - -begin - if not FSelectionLocked then - begin - Assert(Assigned(EndNode), 'EndNode must not be nil!'); - - if StartNode = nil then - StartNode := FRoot.FirstChild - else - if not FullyVisible[StartNode] then - begin - StartNode := GetPreviousVisible(StartNode, True); - if StartNode = nil then - StartNode := FRoot.FirstChild; - end; - - if CompareNodePositions(StartNode, EndNode) < 0 then - begin - NodeFrom := StartNode; - NodeTo := EndNode; - end - else - begin - NodeFrom := EndNode; - NodeTo := StartNode; - end; - - while NodeFrom <> NodeTo do - begin - InternalRemoveFromSelection(NodeFrom); - NodeFrom := GetNextVisible(NodeFrom, True); - end; - // Deselect last node too. - InternalRemoveFromSelection(NodeFrom); - - // Do some housekeeping. - NewSize := PackArray(FSelection, FSelectionCount); - if NewSize > -1 then - begin - FSelectionCount := NewSize; - SetLength(FSelection, FSelectionCount); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateColumnCheckState(Col: TVirtualTreeColumn); - -begin - Col.CheckState := DetermineNextCheckState(Col.CheckType, Col.CheckState); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateDesigner; - -var - ParentForm: TCustomForm; - -begin - if (csDesigning in ComponentState) and not (csUpdating in ComponentState) then - begin - ParentForm := GetParentForm(Self); - if Assigned(ParentForm) and Assigned(ParentForm.Designer) then - ParentForm.Designer.Modified; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateHeaderRect(); - -// Calculates the rectangle the header occupies in non-client area. -// These coordinates are in window rectangle. - -var - OffsetX, - OffsetY: Integer; - EdgeSize: Integer; - Size: TSize; - -begin - FHeaderRect := Rect(0, 0, Width, Height); - - // Consider borders... - if HandleAllocated then begin // Prevent preliminary creation of window handle, see issue #933 - Size := GetBorderDimensions(); - InflateRect(FHeaderRect, Size.cx, Size.cy); - end; - - // ... and bevels. - OffsetX := BorderWidth; - OffsetY := BorderWidth; - if BevelKind <> bkNone then - begin - EdgeSize := 0; - if BevelInner <> bvNone then - Inc(EdgeSize, BevelWidth); - if BevelOuter <> bvNone then - Inc(EdgeSize, BevelWidth); - if beLeft in BevelEdges then - Inc(OffsetX, EdgeSize); - if beTop in BevelEdges then - Inc(OffsetY, EdgeSize); - end; - - InflateRect(FHeaderRect, -OffsetX, -OffsetY); - - if hoVisible in FHeader.Options then - begin - if FHeaderRect.Left <= FHeaderRect.Right then - FHeaderRect.Bottom := FHeaderRect.Top + Integer(FHeader.Height) - else - FHeaderRect := Rect(0, 0, 0, 0); - end - else - FHeaderRect.Bottom := FHeaderRect.Top; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateEditBounds; - -// Used to update the bounds of the current node editor if editing is currently active. - -var - R: TRect; - CurrentAlignment: TAlignment; - CurrentBidiMode: TBidiMode; - offsets : TVTOffsets; - offset : Integer; - -begin - if (tsEditing in FStates) and Assigned(FFocusedNode) and - (FEditColumn < FHeader.Columns.Count) then // prevent EArgumentOutOfRangeException - begin - if (GetCurrentThreadId <> MainThreadID) then - begin - // UpdateEditBounds() will be called at the end of the thread - Exit; - end; - if vsMultiline in FFocusedNode.States then - R := GetDisplayRect(FFocusedNode, FEditColumn, True, False) - else if not (toGridExtensions in FOptions.MiscOptions) then - R := GetDisplayRect(FFocusedNode, FEditColumn, True, True); - - if (toGridExtensions in FOptions.MiscOptions) then - begin - // Use the whole cell when grid extensions are on. - R := GetDisplayRect(FFocusedNode, FEditColumn, False, False); - if FEditColumn = FHeader.MainColumn then - begin - // Calculate an offset for the main column. - GetOffsets(FFocusedNode, offsets, ofsLabel, FEditColumn); - offset := offsets[ofsLabel]; -// if offsets[ofsToggleButton] < 0 then -// Inc(offset, offsets[ofsToggleButton]); - end - else - offset := 0; - - // Adjust edit bounds depending on alignment and bidi mode. - if FEditColumn <= NoColumn then - begin - CurrentAlignment := Alignment; - CurrentBidiMode := BiDiMode; - end - else - begin - CurrentAlignment := FHeader.Columns[FEditColumn].Alignment; - CurrentBidiMode := FHeader.Columns[FEditColumn].BiDiMode; - end; - // Consider bidi mode here. In RTL context does left alignment actually mean right alignment and vice versa. - if CurrentBidiMode <> bdLeftToRight then - ChangeBiDiModeAlignment(CurrentAlignment); - if CurrentAlignment = taLeftJustify then - begin - if CurrentBiDiMode = bdLeftToRight then - Inc(R.Left, offset) - else - Dec(R.Right, offset); - end - else - begin - if CurrentBiDiMode = bdLeftToRight then - Inc(R.Left, offset) - else - Dec(R.Right, offset); - end; - end; - if toShowHorzGridLines in TreeOptions.PaintOptions then - Dec(R.Bottom); - R.Bottom := R.Top + R.Bottom - R.Top; - FEditLink.SetBounds(R); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -const - ScrollMasks: array[Boolean] of Cardinal = (0, SIF_DISABLENOSCROLL); - -const // Region identifiers for GetRandomRgn - CLIPRGN = 1; - METARGN = 2; - APIRGN = 3; - SYSRGN = 4; - -function GetRandomRgn(DC: HDC; Rgn: HRGN; iNum: Integer): Integer; stdcall; external 'GDI32.DLL'; - -procedure TBaseVirtualTree.UpdateWindowAndDragImage(const Tree: TBaseVirtualTree; TreeRect: TRect; UpdateNCArea, - ReshowDragImage: Boolean); - -// Method to repaint part of the window area which is not covered by the drag image and to initiate a recapture -// of the drag image. -// Note: This method must only be called during a drag operation and the tree passed in is the one managing the current -// drag image (so it is the actual drag source). - -var - DragRegion, // the region representing the drag image - UpdateRegion, // the unclipped region within the tree to be updated - NCRegion: HRGN; // the region representing the non-client area of the tree - DragRect, - NCRect: TRect; - RedrawFlags: Cardinal; - - VisibleTreeRegion: HRGN; - - DC: HDC; - - //This function was originally designed only for tree's drag image. But we modified - //it for reusing it with header's drag image too for solving issue 248. - useDragImage: TVTDragImage; -begin - if IntersectRect(TreeRect, TreeRect, ClientRect) then - begin - // Retrieve the visible region of the window. This is important to avoid overpainting parts of other windows - // which overlap this one. - VisibleTreeRegion := CreateRectRgn(0, 0, 1, 1); - DC := GetDCEx(Handle, 0, DCX_CACHE or DCX_WINDOW or DCX_CLIPSIBLINGS or DCX_CLIPCHILDREN); - GetRandomRgn(DC, VisibleTreeRegion, SYSRGN); - ReleaseDC(Handle, DC); - - //Take proper drag image depending on whether the drag is being done in the tree - //or in the header. - useDragImage := Tree.FDragImage; - if (not useDragImage.Visible) - and (Tree.FHeader.DragImage <> nil) and (Tree.FHeader.DragImage.Visible) - then - useDragImage := Tree.FHeader.DragImage; - - // The drag image will figure out itself what part of the rectangle can be recaptured. - // Recapturing is not done by taking a snapshot of the screen, but by letting the tree draw itself - // into the back bitmap of the drag image. So the order here is unimportant. - useDragImage.RecaptureBackground(Self, TreeRect, VisibleTreeRegion, UpdateNCArea, ReshowDragImage); - - // Calculate the screen area not covered by the drag image and which needs an update. - DragRect := useDragImage.GetDragImageRect; - MapWindowPoints(0, Handle, DragRect, 2); - DragRegion := CreateRectRgnIndirect(DragRect); - - // Start with non-client area if requested. - if UpdateNCArea then - begin - // Compute the part of the non-client area which must be updated. - - // Determine the outer rectangle of the entire tree window. - GetWindowRect(Handle, NCRect); - // Express the tree window rectangle in client coordinates (because RedrawWindow wants them so). - MapWindowPoints(0, Handle, NCRect, 2); - NCRegion := CreateRectRgnIndirect(NCRect); - // Determine client rect in screen coordinates and create another region for it. - UpdateRegion := CreateRectRgnIndirect(ClientRect); - // Create a region which only contains the NC part by subtracting out the client area. - CombineRgn(NCRegion, NCRegion, UpdateRegion, RGN_DIFF); - // Subtract also out what is hidden by the drag image. - CombineRgn(NCRegion, NCRegion, DragRegion, RGN_DIFF); - RedrawWindow(Handle, nil, NCRegion, RDW_FRAME or RDW_NOERASE or RDW_NOCHILDREN or RDW_INVALIDATE or RDW_VALIDATE or - RDW_UPDATENOW); - DeleteObject(NCRegion); - DeleteObject(UpdateRegion); - end; - - UpdateRegion := CreateRectRgnIndirect(TreeRect); - RedrawFlags := RDW_INVALIDATE or RDW_VALIDATE or RDW_UPDATENOW or RDW_NOERASE or RDW_NOCHILDREN; - // Remove the part of the update region which is covered by the drag image. - CombineRgn(UpdateRegion, UpdateRegion, DragRegion, RGN_DIFF); - RedrawWindow(Handle, nil, UpdateRegion, RedrawFlags); - DeleteObject(UpdateRegion); - DeleteObject(DragRegion); - DeleteObject(VisibleTreeRegion); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ValidateCache(); - -// Starts cache validation if not already done by adding this instance to the worker thread's waiter list -// (if not already there) and signalling the thread it can start validating. - -begin - // stop validation if it is currently validating this tree's cache. - InterruptValidation(); - - FStartIndex := 0; - if (tsValidationNeeded in FStates) and (FVisibleCount > CacheThreshold) then - begin - // Tell the thread this tree needs actually something to do. - TWorkerThread.AddTree(Self); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ValidateNodeDataSize(var Size: Integer); - -begin - Size := SizeOf(Pointer); - if Assigned(FOnGetNodeDataSize) then - FOnGetNodeDataSize(Self, Size); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.VclStyleChanged(); - - // Updates the member FVclStyleEnabled, should be called initially and when the VCL style changes - -begin - FVclStyleEnabled := StyleServices.Enabled and not StyleServices.IsSystemStyle {$IF CompilerVersion < 35} and not (csDesigning in ComponentState) {$ifend}; - Header.StyleChanged(); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -//PROFILE-NO -procedure TBaseVirtualTree.WndProc(var Message: TMessage); - -var - Handled: Boolean; - -begin - Handled := False; - - // Try the header whether it needs to take this message. - if Assigned(FHeader) and (FHeader.States <> []) then - Handled := FHeader.HandleMessage(Message); - if not Handled then - begin - // For auto drag mode, let tree handle itself, instead of TControl. - if not (csDesigning in ComponentState) and - ((Message.Msg = WM_LBUTTONDOWN) or (Message.Msg = WM_LBUTTONDBLCLK)) then - begin - if (DragMode = dmAutomatic) and (DragKind = dkDrag) then - begin - if IsControlMouseMsg(TWMMouse(Message)) then - Handled := True; - if not Handled then - begin - ControlState := ControlState + [csLButtonDown]; - Dispatch(Message); // overrides TControl's BeginDrag - Handled := True; - end; - end; - end; - - if not Handled and Assigned(FHeader) then - Handled := FHeader.HandleMessage(Message); - - if not Handled then - begin - if (Message.Msg in [WM_NCLBUTTONDOWN, WM_NCRBUTTONDOWN, WM_NCMBUTTONDOWN]) and not Focused then - TrySetFocus; - inherited; - end; - end; -end; -//PROFILE-YES - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WriteChunks(Stream: TStream; Node: PVirtualNode); - -// Writes the core chunks for Node into the stream. -// Note: descendants can optionally override this method to add other node specific chunks. -// Keep in mind that this method is also called for the root node. Using this fact in descendants you can -// create a kind of "global" chunks not directly bound to a specific node. - -var - Header: TChunkHeader; - LastPosition, - ChunkSize: Integer; - Chunk: TBaseChunk; - Run: PVirtualNode; - -begin - with Stream do - begin - // 1. The base chunk... - LastPosition := Position; - Chunk.Header.ChunkType := BaseChunk; - with Node^, Chunk do - begin - Body.ChildCount := ChildCount; - Body.NodeHeight := NodeHeight; - // Some states are only temporary so take them out as they make no sense at the new location. - Body.States := States - [vsChecking, vsCutOrCopy, vsDeleting, vsOnFreeNodeCallRequired, vsHeightMeasured]; - Body.Align := Align; - Body.CheckState := GetCheckState(Node); - Body.CheckType := CheckType; - Body.Reserved := 0; - end; - // write the base chunk - Write(Chunk, SizeOf(Chunk)); - - // 2. ... directly followed by the child node chunks (actually they are child chunks of - // the base chunk) - if vsInitialized in Node.States then - begin - Run := Node.FirstChild; - while Assigned(Run) do - begin - WriteNode(Stream, Run); - Run := Run.NextSibling; - end; - end; - - FinishChunkHeader(Stream, LastPosition, Position); - - // 3. write user data - LastPosition := Position; - Header.ChunkType := UserChunk; - Write(Header, SizeOf(Header)); - DoSaveUserData(Node, Stream); - // check if the application actually wrote data - ChunkSize := Position - LastPosition - SizeOf(TChunkHeader); - // seek back to start of chunk if nothing has been written - if ChunkSize = 0 then - begin - Position := LastPosition; - Size := Size - SizeOf(Header); - end - else - FinishChunkHeader(Stream, LastPosition, Position); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.WriteNode(Stream: TStream; Node: PVirtualNode); - -// Writes the "cover" chunk for Node to Stream and initiates writing child nodes and chunks. - -var - LastPosition: Integer; - Header: TChunkHeader; - -begin - // Initialize the node first if necessary and wanted. - if toInitOnSave in FOptions.MiscOptions then - begin - if not (vsInitialized in Node.States) then - InitNode(Node); - if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then - InitChildren(Node); - end; - - with Stream do - begin - LastPosition := Position; - // Emit the anchor chunk. - Header.ChunkType := NodeChunk; - Write(Header, SizeOf(Header)); - // Write other chunks to stream taking their size into this chunk's size. - WriteChunks(Stream, Node); - - // Update chunk size. - FinishChunkHeader(Stream, LastPosition, Position); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.AbsoluteIndex(Node: PVirtualNode): Cardinal; - -begin - Result := 0; - while Assigned(Node) and (Node <> FRoot) do - begin - if not (vsInitialized in Node.States) then - InitNode(Node); - if Assigned(Node.PrevSibling) then - begin - // if there's a previous sibling then add its total count to the result - Node := Node.PrevSibling; - Inc(Result, Node.TotalCount); - end - else - begin - Node := Node.Parent; - if Node <> FRoot then - Inc(Result); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.AddChild(Parent: PVirtualNode; UserData: Pointer = nil): PVirtualNode; - -// Adds a new node to the given parent node. This is simply done by increasing the child count of the -// parent node. If Parent is nil then the new node is added as (last) top level node. -// UserData can be used to set the first SizeOf(Pointer) bytes of the user data area to an initial value which can be used -// in OnInitNode and will also cause to trigger the OnFreeNode event (if <> nil) even if the node is not yet -// "officially" initialized. -// AddChild is a compatibility method and will implicitly validate the parent node. This is however -// against the virtual paradigm and hence I dissuade from its usage. - -begin - if not (toReadOnly in FOptions.MiscOptions) then - Result := InsertNode(Parent, TVTNodeAttachMode.amAddChildLast, UserData) - else - Result := nil; -end; - -function TBaseVirtualTree.AddChild(Parent: PVirtualNode; const UserData: IInterface): PVirtualNode; -begin - UserData._AddRef(); - Result := AddChild(Parent, Pointer(UserData)); - Include(Result.States, vsReleaseCallOnUserDataRequired); -end; - -function TBaseVirtualTree.AddChild(Parent: PVirtualNode; const UserData: TObject): PVirtualNode; -begin - Result := AddChild(Parent, Pointer(UserData)); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.AddFromStream(Stream: TStream; TargetNode: PVirtualNode); - -// loads nodes from the given stream and adds them to TargetNode -// the current content is not cleared before the load process starts (see also LoadFromStream) - -var - ThisID: TMagicID; - Version, - Count: Cardinal; - Node: PVirtualNode; - -begin - if not (toReadOnly in FOptions.MiscOptions) then - begin - // check first whether this is a stream we can read - Stream.ReadBuffer(ThisID, SizeOf(TMagicID)); - if (ThisID[0] = MagicID[0]) and - (ThisID[1] = MagicID[1]) and - (ThisID[2] = MagicID[2]) and - (ThisID[5] = MagicID[5]) then - begin - Version := Word(ThisID[3]); - if Version <= VTTreeStreamVersion then - begin - BeginUpdate; - try - if Version < 2 then - Count := MaxInt - else - Stream.ReadBuffer(Count, SizeOf(Count)); - - while (Stream.Position < Stream.Size) and (Count > 0) do - begin - Dec(Count); - Node := MakeNewNode; - InternalConnectNode(Node, TargetNode, Self, amAddChildLast); - InternalAddFromStream(Stream, Version, Node); - end; - if TargetNode = FRoot then - DoNodeCopied(nil) - else - DoNodeCopied(TargetNode); - finally - EndUpdate; - end; - end - else - ShowError(SWrongStreamVersion, hcTFWrongStreamVersion); - end - else - ShowError(SWrongStreamVersion, hcTFWrongStreamVersion); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.AfterConstruction; - -begin - inherited; - - if FRoot = nil then - InitRootNode; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.Assign(Source: TPersistent); - -begin - if (Source is TBaseVirtualTree) and not (toReadOnly in FOptions.MiscOptions) then - with Source as TBaseVirtualTree do - begin - Self.Align := Align; - Self.Anchors := Anchors; - Self.AutoScrollDelay := AutoScrollDelay; - Self.AutoScrollInterval := AutoScrollInterval; - Self.AutoSize := AutoSize; - Self.Background := Background; - Self.BevelEdges := BevelEdges; - Self.BevelInner := BevelInner; - Self.BevelKind := BevelKind; - Self.BevelOuter := BevelOuter; - Self.BevelWidth := BevelWidth; - Self.BiDiMode := BiDiMode; - Self.BorderStyle := BorderStyle; - Self.BorderWidth := BorderWidth; - Self.ChangeDelay := ChangeDelay; - Self.CheckImageKind := CheckImageKind; - Self.Color := Color; - Self.Colors.Assign(Colors); - Self.Constraints.Assign(Constraints); - Self.Ctl3D := Ctl3D; - Self.DefaultNodeHeight := DefaultNodeHeight; - Self.DefaultPasteMode := DefaultPasteMode; - Self.DragCursor := DragCursor; - Self.DragImageKind := DragImageKind; - Self.DragKind := DragKind; - Self.DragMode := DragMode; - Self.Enabled := Enabled; - Self.Font := Font; - Self.Header := Header; - Self.HintMode := HintMode; - Self.HotCursor := HotCursor; - Self.Images := Images; - Self.ImeMode := ImeMode; - Self.ImeName := ImeName; - Self.Indent := Indent; - Self.Margin := Margin; - Self.NodeAlignment := NodeAlignment; - Self.NodeDataSize := NodeDataSize; - Self.TreeOptions := TreeOptions; - Self.ParentBiDiMode := ParentBiDiMode; - Self.ParentColor := ParentColor; - Self.ParentCtl3D := ParentCtl3D; - Self.ParentFont := ParentFont; - Self.ParentShowHint := ParentShowHint; - Self.PopupMenu := PopupMenu; - Self.RootNodeCount := RootNodeCount; - Self.ScrollBarOptions := ScrollBarOptions; - Self.ShowHint := ShowHint; - Self.StateImages := StateImages; - Self.StyleElements := StyleElements; - Self.TabOrder := TabOrder; - Self.TabStop := TabStop; - Self.Visible := Visible; - Self.SelectionCurveRadius := SelectionCurveRadius; - Self.SelectionBlendFactor := SelectionBlendFactor; - Self.EmptyListMessage := EmptyListMessage; - end - else - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.AutoScale(isDpiChange: Boolean); - -// If toAutoChangeScale is set, this method ensures that the defaulz node height is set correctly. -// isDPIChnage is True, if the DPI of the form has changed. In this case the font may not yet be adapted to this, so do not adjust DefualtNodeHeight. - -var - lTextHeight: Cardinal; -begin - if HandleAllocated and (toAutoChangeScale in TreeOptions.AutoOptions) and not isDpiChange then - begin - Canvas.Font.Assign(Self.Font); - lTextHeight := Canvas.TextHeight('Tg') + 2; - // By default, we only ensure that DefaultNodeHeight is large enough. - // If the form's dpi has changed, we scale up and down the DefaultNodeHeight, See issue #677. - if (lTextHeight > Self.DefaultNodeHeight) then - Self.DefaultNodeHeight := lTextHeight; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.BeginDrag(Immediate: Boolean; Threshold: Integer); - -// Reintroduced method to allow to start OLE drag'n drop as well as VCL drag'n drop. - -begin - if FDragType = dtVCL then - begin - DoStateChange([tsVCLDragPending]); - inherited; - end - else - if (FStates * [tsOLEDragPending, tsOLEDragging]) = [] then - begin - // Drag start position has already been recorded in WMMouseDown. - if Threshold < 0 then - FDragThreshold := Mouse.DragThreshold - else - FDragThreshold := Threshold; - if Immediate then - DoDragging(FLastClickPos) - else - DoStateChange([tsOLEDragPending]); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.BeginSynch; - -// Starts the synchronous update mode (if not already active). - -begin - if not (csDestroying in ComponentState) then - begin - if FSynchUpdateCount = 0 then - begin - DoUpdating(usBeginSynch); - - // Stop all timers... - StopTimer(ChangeTimer); - StopTimer(StructureChangeTimer); - StopTimer(ExpandTimer); - StopTimer(EditTimer); - StopTimer(HeaderTimer); - StopTimer(ScrollTimer); - StopTimer(SearchTimer); - FSearchBuffer := ''; - FLastSearchNode := nil; - DoStateChange([], [tsEditPending, tsScrollPending, tsScrolling, tsIncrementalSearching]); - - // ...and trigger pending update states. - if tsStructureChangePending in FStates then - DoStructureChange(FLastStructureChangeNode, FLastStructureChangeReason); - if tsChangePending in FStates then - DoChange(FLastChangedNode); - end - else - DoUpdating(usSynch); - end; - Inc(FSynchUpdateCount); - DoStateChange([tsSynchMode]); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.BeginUpdate; - -begin - Assert(GetCurrentThreadId = MainThreadId, 'UI controls like ' + Classname + ' should only be manipulated through the main thread.'); - if not (csDestroying in ComponentState) then - begin - if FUpdateCount = 0 then - begin - DoUpdating(usBegin); - SetUpdateState(True); - end - else - DoUpdating(usUpdate); - end; - Inc(FUpdateCount); - DoStateChange([tsUpdating]); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CancelCutOrCopy; - -// Resets nodes which are marked as being cut. - -var - Run: PVirtualNode; - -begin - if ([tsCutPending, tsCopyPending] * FStates) <> [] then - begin - Run := FRoot.FirstChild; - while Assigned(Run) do - begin - if vsCutOrCopy in Run.States then - Exclude(Run.States, vsCutOrCopy); - Run := GetNextNoInit(Run); - end; - end; - DoStateChange([], [tsCutPending, tsCopyPending]); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CancelEditNode: Boolean; - -// Called by the application or the current edit link to cancel the edit action. - -begin - if HandleAllocated and ([tsEditing, tsEditPending] * FStates <> []) then - Result := DoCancelEdit - else - Result := True; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CancelOperation; - -// Called by the application to cancel a long-running operation. - -begin - if FOperationCount > 0 then - FOperationCanceled := True; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CanEdit(Node: PVirtualNode; Column: TColumnIndex): Boolean; - -// Returns True if the given node can be edited. - -begin - Result := (toEditable in FOptions.MiscOptions) and Enabled and not (toReadOnly in FOptions.MiscOptions) - and ((Column < 0) or (coEditable in FHeader.Columns[Column].Options)); - DoCanEdit(Node, Column, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CanFocus: Boolean; - -var - Form: TCustomForm; - -begin - Result := inherited CanFocus; - - if Result and not (csDesigning in ComponentState) then - begin - Form := GetParentForm(Self); - Result := (Form = nil) or (Form.Enabled and Form.Visible); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.Clear; - -begin - if (not IsEmpty and not (toReadOnly in FOptions.MiscOptions)) or (csDestroying in ComponentState) then - begin - BeginUpdate; - try - InterruptValidation; - if IsEditing then - CancelEditNode; - - if ClipboardStates * FStates <> [] then - begin - OleSetClipboard(nil); - DoStateChange([], ClipboardStates); - end; - ClearSelection; - FFocusedNode := nil; - FLastSelected := nil; - FCurrentHotNode := nil; - FDropTargetNode := nil; - FLastChangedNode := nil; - FRangeAnchor := nil; - FLastVCLDragTarget := nil; - FLastSearchNode := nil; - DeleteChildren(FRoot, True); - FOffsetX := 0; - FOffsetY := 0; - - finally - EndUpdate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ClearChecked; - -var - Node: PVirtualNode; - -begin - Node := RootNode.FirstChild; - while Assigned(Node) do - begin - if Node.CheckState <> csUncheckedNormal then - CheckState[Node] := csUncheckedNormal; - Node := GetNextNoInit(Node); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ClearSelection(); -begin - ClearSelection(True); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ClearDragManager; -begin - Pointer(FDragManager) := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ClearSelection(pFireChangeEvent: Boolean); - -var - Node: PVirtualNode; - Dummy: Integer; - R: TRect; - Counter: Integer; - -begin - Assert(GetCurrentThreadId = MainThreadId, Self.Classname + '.ClearSelection() must only be called from UI thread.'); - if not FSelectionLocked and (FSelectionCount > 0) and not (csDestroying in ComponentState) then - begin - if (FUpdateCount = 0) and HandleAllocated and (FVisibleCount > 0) then - begin - // Iterate through nodes currently visible in the client area and invalidate them. - Node := GetNodeAt(0, 0, True, Dummy); - if Assigned(Node) then - R := GetDisplayRect(Node, NoColumn, False); - Counter := FSelectionCount; - - while Assigned(Node) do - begin - R.Bottom := R.Top + Integer(NodeHeight[Node]); - if vsSelected in Node.States then - begin - InvalidateRect(Handle, @R, False); - Dec(Counter); - // Only try as many nodes as are selected. - if Counter = 0 then - Break; - end; - R.Top := R.Bottom; - if R.Top > ClientHeight then - Break; - Node := GetNextVisibleNoInit(Node, True); - end; - end; - - InternalClearSelection; - if pFireChangeEvent then - Change(nil); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CopyTo(Source: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode; - ChildrenOnly: Boolean): PVirtualNode; - -// A simplified CopyTo method to allow to copy nodes to the root of another tree. - -begin - Result := CopyTo(Source, Tree.FRoot, Mode, ChildrenOnly); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CopyTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode; - ChildrenOnly: Boolean): PVirtualNode; - -// Copies Source and all its child nodes to Target. -// Mode is used to specify further where to add the new node actually (as sibling of Target or as child of Target). -// Result is the newly created node to which source has been copied if ChildrenOnly is False or just contains Target -// in the other case. -// ChildrenOnly determines whether to copy also the source node or only its child nodes. - -var - TargetTree: TBaseVirtualTree; - Stream: TMemoryStream; - -begin - Assert(TreeFromNode(Source) = Self, 'The source tree must contain the source node.'); - - Result := nil; - if (Mode <> amNoWhere) and Assigned(Source) and (Source <> FRoot) then - begin - // Assume that an empty destination means the root in this (the source) tree. - if Target = nil then - begin - TargetTree := Self; - Target := FRoot; - Mode := amAddChildFirst; - end - else - TargetTree := TreeFromNode(Target); - - if not (toReadOnly in TargetTree.TreeOptions.MiscOptions) then - begin - if Target = TargetTree.FRoot then - begin - case Mode of - amInsertBefore: - Mode := amAddChildFirst; - amInsertAfter: - Mode := amAddChildLast; - end; - end; - - Stream := TMemoryStream.Create; - try - // Write all nodes into a temprary stream depending on the ChildrenOnly flag. - if not ChildrenOnly then - WriteNode(Stream, Source) - else - begin - Source := Source.FirstChild; - while Assigned(Source) do - begin - WriteNode(Stream, Source); - Source := Source.NextSibling; - end; - end; - // Now load the serialized nodes into the target node (tree). - TargetTree.BeginUpdate; - try - Stream.Position := 0; - while Stream.Position < Stream.Size do - begin - Result := TargetTree.MakeNewNode; - InternalConnectNode(Result, Target, TargetTree, Mode); - TargetTree.InternalAddFromStream(Stream, VTTreeStreamVersion, Result); - if not DoNodeCopying(Result, Target) then - begin - TargetTree.DeleteNode(Result); - Result := nil; - end - else - DoNodeCopied(Result); - end; - if ChildrenOnly then - Result := Target; - finally - TargetTree.EndUpdate; - end; - finally - Stream.Free; - end; - - with TargetTree do - begin - InvalidateCache; - if FUpdateCount = 0 then - begin - ValidateCache; - UpdateScrollBars(True); - Invalidate; - end; - StructureChange(Source, crNodeCopied); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CopyToClipboard; - -var - DataObject: IDataObject; - -begin - if FSelectionCount > 0 then - begin - DataObject := TVTDataObject.Create(Self, True) as IDataObject; - if OleSetClipboard(DataObject) = S_OK then - begin - MarkCutCopyNodes; - DoStateChange([tsCopyPending]); - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.CutToClipboard; -begin - if (FSelectionCount > 0) and not (toReadOnly in FOptions.MiscOptions) then - begin - if OleSetClipboard(TVTDataObject.Create(Self, True)) = S_OK then - begin - MarkCutCopyNodes; - DoStateChange([tsCutPending], [tsCopyPending]); - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DeleteChildren(Node: PVirtualNode; ResetHasChildren: Boolean = False); - -// Removes all children and their children from memory without changing the vsHasChildren style by default. - -var - Run, - Mark: PVirtualNode; - LastTop, - LastLeft, - NewSize: Integer; - ParentVisible: Boolean; - -begin - if Assigned(Node) and (Node.ChildCount > 0) and not (toReadOnly in FOptions.MiscOptions) then - begin - Assert(not (tsIterating in FStates), 'Deleting nodes during tree iteration leads to invalid pointers.'); - - // The code below uses some flags for speed improvements which may cause invalid pointers if updates of - // the tree happen. Hence switch updates off until we have finished the operation. - Inc(FUpdateCount); - try - InterruptValidation; - LastLeft := -FEffectiveOffsetX; - LastTop := FOffsetY; - - // Make a local copy of the visibility state of this node to speed up - // adjusting the visible nodes count. - ParentVisible := Node = FRoot; - if not ParentVisible then - ParentVisible := FullyVisible[Node] and (vsExpanded in Node.States); - - // Show that we are clearing the child list, to avoid registering structure change events. - Run := Node.LastChild; - while Assigned(Run) do - begin - if ParentVisible and IsEffectivelyVisible[Run] then - Dec(FVisibleCount); - - Include(Run.States, vsDeleting); - Mark := Run; - Run := Run.PrevSibling; - // Important, to avoid exchange of invalid pointers while disconnecting the node. - if Assigned(Run) then - Run.NextSibling := nil; - DeleteNode(Mark, False, True); - end; - if ResetHasChildren then - Exclude(Node.States, vsHasChildren); - if Node <> FRoot then - Exclude(Node.States, vsExpanded); - Node.ChildCount := 0; - if (Node = FRoot) or (vsDeleting in Node.States) then - begin - Node.TotalHeight := FDefaultNodeHeight + NodeHeight[Node]; - Node.TotalCount := 1; - end - else - begin - AdjustTotalHeight(Node, NodeHeight[Node]); - AdjustTotalCount(Node, 1); - end; - Node.FirstChild := nil; - Node.LastChild := nil; - finally - Dec(FUpdateCount); - end; - - InvalidateCache; - if FUpdateCount = 0 then - begin - NewSize := PackArray(FSelection, FSelectionCount); - if NewSize > -1 then - begin - FSelectionCount := NewSize; - SetLength(FSelection, FSelectionCount); - end; - - ValidateCache; - UpdateScrollBars(True); - // Invalidate entire tree if it scrolled e.g. to make the last node also the - // bottom node in the treeview. - if (LastLeft <> FOffsetX) or (LastTop <> FOffsetY) then - Invalidate - else - InvalidateToBottom(Node); - if tsChangePending in FStates then begin - DoChange(FLastChangedNode); - EnsureNodeSelected(); - end; - end; - StructureChange(Node, crChildDeleted); - end - else if ResetHasChildren then - Exclude(Node.States, vsHasChildren); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DeleteNode(Node: PVirtualNode; Reindex: Boolean; ParentClearing: Boolean); - -var - LastTop, - LastLeft: Integer; - LastParent: PVirtualNode; - WasInSynchMode: Boolean; - -begin - if Assigned(Node) and (Node <> FRoot) and not (toReadOnly in FOptions.MiscOptions) then - begin - Assert(not (tsIterating in FStates), 'Deleting nodes during tree iteration leads to invalid pointers.'); - - // Determine parent node for structure change notification. - LastParent := Node.Parent; - - if not ParentClearing then - begin - if LastParent = FRoot then - StructureChange(nil, crChildDeleted) - else - StructureChange(LastParent, crChildDeleted); - if Node = FNextNodeToSelect then - FNextNodeToSelect := nil; - end; - - LastLeft := -FEffectiveOffsetX; - LastTop := FOffsetY; - - if tsHint in FStates then - begin - Application.CancelHint; - DoStateChange([], [tsHint]); - end; - - if not ParentClearing then - InterruptValidation; - - DeleteChildren(Node); - - if vsSelected in Node.States then - begin - if FUpdateCount = 0 then - begin - // Go temporarily into sync mode to avoid a delayed change event for the node - // when unselecting. - WasInSynchMode := tsSynchMode in FStates; - Include(FStates, tsSynchMode); - RemoveFromSelection(Node); - //EnsureNodeSelected(); // also done in DoFreeNode() - if not WasInSynchMode then - Exclude(FStates, tsSynchMode); - InvalidateToBottom(LastParent); - end - else - InternalRemoveFromSelection(Node); - end - else - InvalidateToBottom(LastParent); - - InternalDisconnectNode(Node, False, Reindex); - DoFreeNode(Node); - - if not ParentClearing then - begin - DetermineHiddenChildrenFlag(LastParent); - InvalidateCache; - if FUpdateCount = 0 then - begin - ValidateCache; - UpdateScrollBars(True); - // Invalidate entire tree if it scrolled e.g. to make the last node also the - // bottom node in the treeview. - if (LastLeft <> FOffsetX) or (LastTop <> FOffsetY) then - Invalidate; - end; - end; - end; -end; - -procedure TBaseVirtualTree.DeleteNode(Node: PVirtualNode; pReIndex: Boolean = True); -begin - DeleteNode(Node, pReIndex, False); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DeleteNodes(const pNodes: TNodeArray); - - // Deletes all given nodes. - // Best performance is achieved if nodes are sorted by parent - -var - I: Integer; - LevelChange: Boolean; -begin - BeginUpdate; - try - for I := High(pNodes) downto 1 do - begin - LevelChange := pNodes[I].Parent <> pNodes[I - 1].Parent; - DeleteNode(pNodes[I], LevelChange, False); - end; - DeleteNode(pNodes[0]); - finally - EndUpdate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DeleteSelectedNodes; - -// Deletes all currently selected nodes (including their child nodes). - -var - lNodes: TNodeArray; -begin - lNodes := nil; - if (FSelectionCount > 0) and not (toReadOnly in FOptions.MiscOptions) then - begin - lNodes := GetSortedSelection(True); - DeleteNodes(lNodes); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.Dragging: Boolean; - -begin - // Check for both OLE drag'n drop as well as VCL drag'n drop. - Result := ([tsOLEDragPending, tsOLEDragging] * FStates <> []) or inherited Dragging; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.EditNode(Node: PVirtualNode; Column: TColumnIndex): Boolean; - -// Application triggered edit event for the given node. -// Returns True if the tree started editing otherwise False. - -begin - Assert(Assigned(Node), 'Node must not be nil.'); - Assert((Column > InvalidColumn) and (Column < FHeader.Columns.Count), - 'Column must be a valid column index (-1 if no header is shown).'); - - Result := tsEditing in FStates; - // If the tree is already editing then we don't disrupt this. - if not Result and not (toReadOnly in FOptions.MiscOptions) then - begin - FocusedNode := Node; - if Assigned(FFocusedNode) and (Node = FFocusedNode) and CanEdit(FFocusedNode, Column) then - begin - FEditColumn := Column; - if not (vsInitialized in Node.States) then - InitNode(Node); - DoEdit; - Result := tsEditing in FStates; - end - else - Result := False; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.EndEditNode: Boolean; - -// Called to finish a current edit action or stop the edit timer if an edit operation is pending. -// Returns True if editing was successfully ended or the control was not in edit mode -// Returns False if the control could not leave the edit mode e.g. due to an invalid value that was entered. - -begin - if [tsEditing, tsEditPending] * FStates <> [] then - Result := DoEndEdit - else - Result := True; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.EndSynch; - -begin - if FSynchUpdateCount > 0 then - Dec(FSynchUpdateCount); - - if not (csDestroying in ComponentState) then - begin - if FSynchUpdateCount = 0 then - begin - DoStateChange([], [tsSynchMode]); - DoUpdating(usEndSynch); - end - else - DoUpdating(usSynch); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.EndUpdate; - -var - NewSize: Integer; - -begin - if FUpdateCount > 0 then - Dec(FUpdateCount); - - if not (csDestroying in ComponentState) then - begin - if (FUpdateCount = 0) and (tsUpdating in FStates) then - begin - if tsUpdateHiddenChildrenNeeded in FStates then - begin - DetermineHiddenChildrenFlagAllNodes; - Exclude(FStates, tsUpdateHiddenChildrenNeeded); - end; - - DoStateChange([], [tsUpdating]); - - NewSize := PackArray(FSelection, FSelectionCount); - if NewSize > -1 then - begin - FSelectionCount := NewSize; - SetLength(FSelection, FSelectionCount); - end; - - InvalidateCache; - ValidateCache; - if HandleAllocated then - UpdateScrollBars(False); - - if tsStructureChangePending in FStates then - DoStructureChange(FLastStructureChangeNode, FLastStructureChangeReason); - try - if tsChangePending in FStates then - DoChange(FLastChangedNode); - finally - if toAutoSort in FOptions.AutoOptions then - SortTree(FHeader.SortColumn, FHeader.SortDirection, True); - - SetUpdateState(False); - if HandleAllocated then - Invalidate; - UpdateDesigner; - end; - if Assigned(FAccessibleItem) then // See issue #1174 - NotifyWinEvent(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT, CHILDID_SELF); - end; - - if FUpdateCount = 0 then begin - DoUpdating(usEnd); - EnsureNodeSelected(); - end - else - DoUpdating(usUpdate); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ExecuteAction(Action: TBasicAction): Boolean; - -// Some support for standard actions. - -begin - Result := inherited ExecuteAction(Action); - - if not Result then - begin - Result := Action is TEditSelectAll; - if Result then - SelectAll(False) - else - begin - Result := Action is TEditCopy; - if Result then - CopyToClipboard - else - if not (toReadOnly in FOptions.MiscOptions) then - begin - Result := Action is TEditCut; - if Result then - CutToClipboard - else - begin - Result := Action is TEditPaste; - if Result then - PasteFromClipboard - else - begin - Result := Action is TEditDelete; - if Result then - DeleteSelectedNodes; - end; - end; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.FinishCutOrCopy; - -// Deletes nodes which are marked as being cutted. - -var - Run: PVirtualNode; - -begin - if tsCutPending in FStates then - begin - Run := FRoot.FirstChild; - while Assigned(Run) do - begin - if vsCutOrCopy in Run.States then - DeleteNode(Run); - Run := GetNextNoInit(Run); - end; - DoStateChange([], [tsCutPending]); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.FlushClipboard; - -// Used to render the data which is currently on the clipboard (finishes delayed rendering). - -begin - if ClipboardStates * FStates <> [] then - begin - DoStateChange([tsClipboardFlushing]); - OleFlushClipboard; - CancelCutOrCopy; - DoStateChange([], [tsClipboardFlushing]); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.FullCollapse(Node: PVirtualNode = nil); - -// This routine collapses all expanded nodes in the subtree given by Node or the whole tree if Node is FRoot or nil. -// Only nodes which are expanded will be collapsed. This excludes uninitialized nodes but nodes marked as visible -// will still be collapsed if they are expanded. - -var - Stop: PVirtualNode; - -begin - if FRoot.TotalCount > 1 then - begin - if Node = FRoot then - Node := nil; - - DoStateChange([tsCollapsing]); - BeginUpdate; - try - Stop := Node; - Node := GetLastVisibleNoInit(Node, True); - - if Assigned(Node) then - begin - repeat - if [vsHasChildren, vsExpanded] * Node.States = [vsHasChildren, vsExpanded] then - ToggleNode(Node); - Node := GetPreviousNoInit(Node, True); - until (Node = Stop) or not Assigned(Node); - - // Collapse the start node too. - if Assigned(Stop) and ([vsHasChildren, vsExpanded] * Stop.States = [vsHasChildren, vsExpanded]) then - ToggleNode(Stop); - end; - finally - EndUpdate; - DoStateChange([], [tsCollapsing]); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.FullExpand(Node: PVirtualNode = nil); - -// This routine expands all collapsed nodes in the subtree given by Node or the whole tree if Node is FRoot or nil. -// All nodes on the way down are initialized so this procedure might take a long time. -// Since all nodes are validated, the tree cannot make use of optimatizations. Hence it is counter productive and you -// should consider avoiding its use. - -var - Stop: PVirtualNode; - -begin - if FRoot.TotalCount > 1 then - begin - DoStateChange([tsExpanding]); - StartOperation(TVTOperationKind.okExpand); - BeginUpdate; - try - if Node = nil then - begin - Node := FRoot.FirstChild; - Stop := nil; - end - else - begin - Stop := Node.NextSibling; - if Stop = nil then - begin - Stop := Node; - repeat - Stop := Stop.Parent; - until (Stop = FRoot) or Assigned(Stop.NextSibling); - if Stop = FRoot then - Stop := nil - else - Stop := Stop.NextSibling; - end; - end; - - // Initialize the start node. Others will be initialized in GetNext. - if not (vsInitialized in Node.States) then - InitNode(Node); - - repeat - if not (vsExpanded in Node.States) then - ToggleNode(Node); - Node := GetNext(Node); - until (Node = Stop) or OperationCanceled; - finally - EndOperation(TVTOperationKind.okExpand); - EndUpdate; - DoStateChange([], [tsExpanding]); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetControlsAlignment: TAlignment; - -begin - Result := FAlignment; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetDisplayRect(Node: PVirtualNode; Column: TColumnIndex; TextOnly: Boolean; - Unclipped: Boolean = False; ApplyCellContentMargin: Boolean = False): TRect; - -// Determines the client coordinates the given node covers, depending on scrolling, expand state etc. -// If the given node cannot be found (because one of its parents is collapsed or it is invisible) then an empty -// rectangle is returned. -// If TextOnly is True then only the text bounds are returned, that is, the resulting rectangle's left and right border -// are updated according to bidi mode, alignment and text width of the node. -// If Unclipped is True (which only makes sense if also TextOnly is True) then the calculated text rectangle is -// not clipped if the text does not entirely fit into the text space. This is special handling needed for hints. -// If ApplyCellContentMargin is True (which only makes sense if also TextOnly is True) then the calculated text -// rectangle respects the cell content margin. -// If Column is -1 then the entire client width is used before determining the node's width otherwise the bounds of the -// particular column are used. -// Note: Column must be a valid column and is used independent of whether the header is visible or not. - -var - Temp: PVirtualNode; - LeftOffset: Cardinal; - TopOffset: Cardinal; - CacheIsAvailable: Boolean; - TextWidth: Integer; - CurrentBidiMode: TBidiMode; - CurrentAlignment: TAlignment; - MaxUnclippedHeight: Integer; - TM: TTextMetric; - ExtraVerticalMargin: Integer; - lOffsets: TVTOffsets; -begin - Assert(Assigned(Node), 'Node must not be nil.'); - Assert(Node <> FRoot, 'Node must not be the hidden root node.'); - - if not (vsInitialized in Node.States) then - InitNode(Node); - - Result := Rect(0, 0, 0, 0); - - // Check whether the node is visible (determine indentation level btw.). - if not IsEffectivelyVisible[Node] then - Exit; - - // Here we know the node is visible. - TopOffset := 0; - CacheIsAvailable := False; - if tsUseCache in FStates then - begin - // If we can use the position cache then do a binary search to find a cached node which is as close as possible - // to the current node. Iterate then through all following and visible nodes and sum up their heights. - Temp := FindInPositionCache(Node, TopOffset); - CacheIsAvailable := Assigned(Temp); - while Assigned(Temp) and (Temp <> Node) do - begin - Inc(TopOffset, NodeHeight[Temp]); - Temp := GetNextVisibleNoInit(Temp, True); - end; - end; - if not CacheIsAvailable then - begin - // If the cache is not available then go straight through all nodes up to the root and sum up their heights. - Temp := Node; - repeat - Temp := GetPreviousVisibleNoInit(Temp, True); - if Temp = nil then - Break; - Inc(TopOffset, NodeHeight[Temp]); - until False; - end; - - Result := Rect(0, TopOffset, Max(FRangeX, ClientWidth), TopOffset + NodeHeight[Node]); - - // Limit left and right bounds to the given column (if any) and move bounds according to current scroll state. - if Column > NoColumn then - begin - FHeader.Columns.GetColumnBounds(Column, Result.Left, Result.Right); - // The right column border is not part of this cell. - Dec(Result.Right); - OffsetRect(Result, 0, FOffsetY); - end - else - OffsetRect(Result, -FEffectiveOffsetX, FOffsetY); - - // Limit left and right bounds further if only the text area is required. - if TextOnly then - begin - // If the text of a node is involved then we have to consider directionality and alignment too. - if Column <= NoColumn then - begin - CurrentBidiMode := BidiMode; - CurrentAlignment := Alignment; - end - else - begin - CurrentBidiMode := FHeader.Columns[Column].BidiMode; - CurrentAlignment := FHeader.Columns[Column].Alignment; - end; - - GetOffsets(Node, lOffsets, TVTElement.ofsLabel, Column); - LeftOffset := lOffSets[TVTElement.ofsLabel]; - // Offset contains now the distance from the left or right border of the rectangle (depending on bidi mode). - // Now consider the alignment too and calculate the final result. - if CurrentBidiMode = bdLeftToRight then - begin - Inc(Result.Left, LeftOffset); - // Left-to-right reading does not need any special adjustment of the alignment. - end - else - begin - Dec(Result.Right, LeftOffset); - - // Consider bidi mode here. In RTL context does left alignment actually mean right alignment and vice versa. - ChangeBiDiModeAlignment(CurrentAlignment); - end; - - TextWidth := DoGetNodeWidth(Node, Column); - - // Keep cell height before applying cell content margin in order to increase cell height if text does not fit - // and Unclipped it true (see below). - MaxUnclippedHeight := Result.Bottom - Result.Top; - - if ApplyCellContentMargin then - DoBeforeCellPaint(Self.Canvas, Node, Column, cpmGetContentMargin, Result, Result); - - if Unclipped then - begin - // The caller requested the text coordinates unclipped. This means they must be calculated so as would - // there be enough space, regardless of column bounds etc. - // The layout still depends on the available space too, because this determines the position - // of the unclipped text rectangle. - if Result.Right - Result.Left < TextWidth - 1 then - if CurrentBidiMode = bdLeftToRight then - CurrentAlignment := taLeftJustify - else - CurrentAlignment := taRightJustify; - - // Increase cell height (up to MaxUnclippedHeight determined above) if text does not fit. - GetTextMetrics(Self.Canvas.Handle, TM); - ExtraVerticalMargin := System.Math.Min(TM.tmHeight, MaxUnclippedHeight) - (Result.Bottom - Result.Top); - if ExtraVerticalMargin > 0 then - InflateRect(Result, 0, (ExtraVerticalMargin + 1) div 2); - - case CurrentAlignment of - taCenter: - begin - Result.Left := (Result.Left + Result.Right - TextWidth) div 2; - Result.Right := Result.Left + TextWidth; - end; - taRightJustify: - Result.Left := Result.Right - TextWidth; - else // taLeftJustify - Result.Right := Result.Left + TextWidth - 1; - end; - end - else - // Modify rectangle only if the text fits entirely into the given room. - if Result.Right - Result.Left > TextWidth then - case CurrentAlignment of - taCenter: - begin - Result.Left := (Result.Left + Result.Right - TextWidth) div 2; - Result.Right := Result.Left + TextWidth; - end; - taRightJustify: - Result.Left := Result.Right - TextWidth; - else // taLeftJustify - Result.Right := Result.Left + TextWidth; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetEffectivelyFiltered(Node: PVirtualNode): Boolean; - -// Checks if a node is effectively filtered out. This depends on the nodes state and the paint options. - -begin - if Assigned(Node) then - Result := (vsFiltered in Node.States) and not (toShowFilteredNodes in FOptions.PaintOptions) - else - Result := False; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetEffectivelyVisible(Node: PVirtualNode): Boolean; - -begin - Result := (vsVisible in Node.States) and not IsEffectivelyFiltered[Node]; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirst(ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the first node in the tree while optionally considering toChildrenAbove. - -begin - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - if vsHasChildren in FRoot.States then - begin - Result := FRoot; - - // Child nodes are the first choice if possible. - if Assigned(Result.FirstChild) then - begin - while Assigned(Result.FirstChild) do - begin - Result := Result.FirstChild; - if not (vsInitialized in Result.States) then - InitNode(Result); - - if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then - InitChildren(Result); - end; - end - else - Result := nil; - end - else - Result := nil; - end - else - Result := FRoot.FirstChild; - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstChecked(State: TCheckState = csCheckedNormal; - ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the first node in the tree with the given check state. - -begin - Result := GetNextChecked(nil, State, ConsiderChildrenAbove); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstChild(Node: PVirtualNode): PVirtualNode; - -// Returns the first child of the given node. The result node is initialized before exit. - -begin - if (Node = nil) or (Node = FRoot) then - Result := FRoot.FirstChild - else - begin - if not (vsInitialized in Node.States) then - InitNode(Node); - if vsHasChildren in Node.States then - begin - if Node.ChildCount = 0 then - InitChildren(Node); - Result := Node.FirstChild; - end - else - Result := nil; - end; - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstChildNoInit(Node: PVirtualNode): PVirtualNode; -// Determines the first child of the given node but does not initialize it. - -begin - if (Node = nil) or (Node = FRoot) then - Result := FRoot.FirstChild - else - begin - if vsHasChildren in Node.States then - Result := Node.FirstChild - else - Result := nil; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstCutCopy(ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the first node in the tree which is currently marked for a clipboard operation. -// See also GetNextCutCopy for comments on initialization. - -begin - Result := GetNextCutCopy(nil, ConsiderChildrenAbove); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstInitialized(ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the first node which is already initialized. - -begin - Result := GetFirstNoInit(ConsiderChildrenAbove); - if Assigned(Result) and not (vsInitialized in Result.States) then - Result := GetNextInitialized(Result, ConsiderChildrenAbove); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstLeaf: PVirtualNode; - -// Returns the first node in the tree which has currently no children. -// The result is initialized if necessary. - -begin - Result := GetNextLeaf(nil); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstLevel(NodeLevel: Cardinal): PVirtualNode; - -// Returns the first node in the tree on a specific level. -// The result is initialized if necessary. - -begin - Result := GetFirstNoInit(True); - while Assigned(Result) and (GetNodeLevel(Result) <> NodeLevel) do - Result := GetNextNoInit(Result, True); - - if Assigned(Result) and (GetNodeLevel(Result) <> NodeLevel) then // i.e. there is no node with the desired level in the tree - Result := nil; - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstNoInit(ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the first node in the tree while optionally considering toChildrenAbove. -// No initialization is performed. - -begin - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - if vsHasChildren in FRoot.States then - begin - Result := FRoot; - - // Child nodes are the first choice if possible. - if Assigned(Result.FirstChild) then - begin - while Assigned(Result.FirstChild) do - Result := Result.FirstChild; - end - else - Result := nil; - end - else - Result := nil; - end - else - Result := FRoot.FirstChild; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstSelected(ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the first node in the current selection while optionally considering toChildrenAbove. - -begin - Result := GetNextSelected(nil, ConsiderChildrenAbove); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; - IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the first visible node in the tree while optionally considering toChildrenAbove. -// If necessary nodes are initialized on demand. - -begin - Result := Node; - if not Assigned(Result) then - Result := FRoot; - - if vsHasChildren in Result.States then - begin - if Result.ChildCount = 0 then - InitChildren(Result); - - // Child nodes are the first choice if possible. - if Assigned(Result.FirstChild) then - begin - Result := GetFirstChild(Result); - - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - repeat - // Search the first visible sibling. - while Assigned(Result.NextSibling) and not (vsVisible in Result.States) do - begin - Result := Result.NextSibling; - // Init node on demand as this might change the visibility. - if not (vsInitialized in Result.States) then - InitNode(Result); - end; - - // If there are no visible siblings take the parent. - if not (vsVisible in Result.States) then - begin - Result := Result.Parent; - if Result = FRoot then - Result := nil; - Break; - end - else - begin - if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then - InitChildren(Result); - if (not Assigned(Result.FirstChild)) or (not (vsExpanded in Result.States)) then - Break; - end; - - Result := Result.FirstChild; - if not (vsInitialized in Result.States) then - InitNode(Result); - until False; - end - else - begin - // If there are no children or the first child is not visible then search the sibling nodes or traverse parents. - if not (vsVisible in Result.States) then - begin - repeat - // Is there a next sibling? - if Assigned(Result.NextSibling) then - begin - Result := Result.NextSibling; - // The visible state can be removed during initialization so init the node first. - if not (vsInitialized in Result.States) then - InitNode(Result); - if vsVisible in Result.States then - Break; - end - else - begin - // No sibling anymore, so use the parent's next sibling. - if Result.Parent <> FRoot then - Result := Result.Parent - else - begin - // There are no further nodes to examine, hence there is no further visible node. - Result := nil; - Break; - end; - end; - until False; - end; - end; - end - else - Result := nil; - end - else - Result := nil; - - if Assigned(Result) and not IncludeFiltered and IsEffectivelyFiltered[Result] then - Result := GetNextVisible(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the first visible child node of Node. If necessary nodes are initialized on demand. - -begin - if Node = nil then - Node := FRoot; - Result := GetFirstChild(Node); - - if Assigned(Result) and (not (vsVisible in Result.States) or - (not IncludeFiltered and IsEffectivelyFiltered[Result])) then - Result := GetNextVisibleSibling(Result, IncludeFiltered); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the first visible child node of Node. - -begin - if Node = nil then - Node := FRoot; - Result := Node.FirstChild; - if Assigned(Result) and (not (vsVisible in Result.States) or - (not IncludeFiltered and IsEffectivelyFiltered[Result])) then - Result := GetNextVisibleSiblingNoInit(Result, IncludeFiltered); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstVisibleNoInit(Node: PVirtualNode = nil; - ConsiderChildrenAbove: Boolean = True; IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the first visible node in the tree or given subtree while optionally considering toChildrenAbove. -// No initialization is performed. - -begin - Result := Node; - if not Assigned(Result) then - Result := FRoot; - - if vsHasChildren in Result.States then - begin - // Child nodes are the first choice if possible. - if Assigned(Result.FirstChild) then - begin - Result := Result.FirstChild; - - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - repeat - // Search the first visible sibling. - while Assigned(Result.NextSibling) and not (vsVisible in Result.States) do - Result := Result.NextSibling; - - // If there a no visible siblings take the parent. - if not (vsVisible in Result.States) then - begin - Result := Result.Parent; - if Result = FRoot then - Result := nil; - Break; - end - else - if (not Assigned(Result.FirstChild)) or (not (vsExpanded in Result.States))then - Break; - - Result := Result.FirstChild; - until False; - end - else - begin - // If there are no children or the first child is not visible then search the sibling nodes or traverse parents. - if not (vsVisible in Result.States) then - begin - repeat - // Is there a next sibling? - if Assigned(Result.NextSibling) then - begin - Result := Result.NextSibling; - if vsVisible in Result.States then - Break; - end - else - begin - // No sibling anymore, so use the parent's next sibling. - if Result.Parent <> FRoot then - Result := Result.Parent - else - begin - // There are no further nodes to examine, hence there is no further visible node. - Result := nil; - Break; - end; - end; - until False; - end; - end; - end - else - Result := nil; - end - else - Result := nil; - - if Assigned(Result) and not IncludeFiltered and IsEffectivelyFiltered[Result] then - Result := GetNextVisibleNoInit(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.GetHitTestInfoAt(X, Y: Integer; Relative: Boolean; var HitInfo: THitInfo); - -// Determines the node that occupies the specified point or nil if there's none. The parameter Relative determines -// whether to consider X and Y as being client coordinates (if True) or as being absolute tree coordinates. -// HitInfo is filled with flags describing the hit further. - -var - ColLeft, - ColRight: Integer; - NodeTop: Integer; - InitialColumn, - NextColumn: TColumnIndex; - CurrentBidiMode: TBidiMode; - CurrentAlignment: TAlignment; - NodeRect: TRect; - -begin - HitInfo.HitNode := nil; - HitInfo.HitPositions := []; - HitInfo.HitColumn := NoColumn; - - // Determine if point lies in the tree's client area. - if X < 0 then - Include(HitInfo.HitPositions, hiToLeft) - else - if X > Max(FRangeX, ClientWidth) then - Include(HitInfo.HitPositions, hiToRight); - - if Y < 0 then - Include(HitInfo.HitPositions, hiAbove) - else - if Y > Max(FRangeY, ClientHeight) then - Include(HitInfo.HitPositions, hiBelow); - - // Convert position into absolute coordinate if necessary. - if Relative then - begin - if X >= Header.Columns.GetVisibleFixedWidth then - Inc(X, FEffectiveOffsetX); - Inc(Y, -FOffsetY); - end; - HitInfo.HitPoint.X := X; - HitInfo.HitPoint.Y := Y; - - // If the point is in the tree area then check the nodes. - if HitInfo.HitPositions = [] then - begin - HitInfo.HitNode := GetNodeAt(X, Y, False, NodeTop); - if HitInfo.HitNode = nil then - Include(HitInfo.HitPositions, hiNowhere) - else - begin - // At this point we need some info about the node, so it must be initialized. - if not (vsInitialized in HitInfo.HitNode.States) then - InitNode(HitInfo.HitNode); - - if FHeader.UseColumns then - begin - HitInfo.HitColumn := FHeader.Columns.GetColumnAndBounds(Point(X, Y), ColLeft, ColRight, False); - // If auto column spanning is enabled then look for the last non empty column. - if toAutoSpanColumns in FOptions.AutoOptions then - begin - InitialColumn := HitInfo.HitColumn; - // Search to the left of the hit column for empty columns. - while (HitInfo.HitColumn > NoColumn) and ColumnIsEmpty(HitInfo.HitNode, HitInfo.HitColumn) do - begin - NextColumn := FHeader.Columns.GetPreviousVisibleColumn(HitInfo.HitColumn); - if NextColumn = InvalidColumn then - Break; - HitInfo.HitColumn := NextColumn; - Dec(ColLeft, FHeader.Columns[NextColumn].Width); - end; - // Search to the right of the hit column for empty columns. - repeat - InitialColumn := FHeader.Columns.GetNextVisibleColumn(InitialColumn); - if (InitialColumn = InvalidColumn) or not ColumnIsEmpty(HitInfo.HitNode, InitialColumn) then - Break; - Inc(ColRight, FHeader.Columns[InitialColumn].Width); - until False; - end; - // Make the X position and the right border relative to the start of the column. - Dec(X, ColLeft); - Dec(ColRight, ColLeft); - end - else - begin - HitInfo.HitColumn := NoColumn; - ColRight := Max(FRangeX, ClientWidth); - end; - ColLeft := 0; - - if HitInfo.HitColumn = InvalidColumn then - Include(HitInfo.HitPositions, hiNowhere) - else - begin - // From now on X is in "column" coordinates (relative to the left column border). - HitInfo.HitPositions := [hiOnItem]; - - // Avoid getting the display rect if this is not necessary. - if toNodeHeightResize in FOptions.MiscOptions then - begin - NodeRect := GetDisplayRect(HitInfo.HitNode, HitInfo.HitColumn, False); - if Y <= (NodeRect.Top - FOffsetY + 1) then - Include(HitInfo.HitPositions, hiUpperSplitter) - else - if Y >= (NodeRect.Bottom - FOffsetY - 3) then - Include(HitInfo.HitPositions, hiLowerSplitter); - end; - - if HitInfo.HitColumn <= NoColumn then - begin - CurrentBidiMode := BidiMode; - CurrentAlignment := Alignment; - end - else - begin - CurrentBidiMode := FHeader.Columns[HitInfo.HitColumn].BidiMode; - CurrentAlignment := FHeader.Columns[HitInfo.HitColumn].Alignment; - end; - - if CurrentBidiMode = bdLeftToRight then - DetermineHitPositionLTR(HitInfo, X, ColRight, CurrentAlignment) - else - DetermineHitPositionRTL(HitInfo, X, ColRight, CurrentAlignment); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetLast(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the very last node in the tree branch given by Node and initializes the nodes all the way down including the -// result. toChildrenAbove is optionally considered. By using Node = nil the very last node in the tree is returned. - -var - Next: PVirtualNode; - -begin - Result := GetLastChild(Node); - if not ConsiderChildrenAbove or not (toChildrenAbove in FOptions.PaintOptions) then - while Assigned(Result) do - begin - // Test if there is a next last child. If not keep the node from the last run. - // Otherwise use the next last child. - Next := GetLastChild(Result); - if Next = nil then - Break; - Result := Next; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetLastInitialized(Node: PVirtualNode = nil; - ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the very last initialized child node in the tree branch given by Node. - -begin - Result := GetLastNoInit(Node, ConsiderChildrenAbove); - if Assigned(Result) and not (vsInitialized in Result.States) then - Result := GetPreviousInitialized(Result, ConsiderChildrenAbove); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetLastNoInit(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the very last node in the tree branch given by Node without initialization. - -var - Next: PVirtualNode; - -begin - Result := GetLastChildNoInit(Node); - if not ConsiderChildrenAbove or not (toChildrenAbove in FOptions.PaintOptions) then - while Assigned(Result) do - begin - // Test if there is a next last child. If not keep the node from the last run. - // Otherwise use the next last child. - Next := GetLastChildNoInit(Result); - if Next = nil then - Break; - Result := Next; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetLastChild(Node: PVirtualNode): PVirtualNode; - -// Determines the last child of the given node and initializes it if there is one. - -begin - if (Node = nil) or (Node = FRoot) then - Result := FRoot.LastChild - else - begin - if not (vsInitialized in Node.States) then - InitNode(Node); - if vsHasChildren in Node.States then - begin - if Node.ChildCount = 0 then - InitChildren(Node); - Result := Node.LastChild; - end - else - Result := nil; - end; - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetLastChildNoInit(Node: PVirtualNode): PVirtualNode; - -// Determines the last child of the given node but does not initialize it. - -begin - if (Node = nil) or (Node = FRoot) then - Result := FRoot.LastChild - else - begin - if vsHasChildren in Node.States then - Result := Node.LastChild - else - Result := nil; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetLastVisible(Node: PVirtualNode = nil; ConsiderChildrenAbove: Boolean = True; - IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the very last visible node in the tree while optionally considering toChildrenAbove. -// The nodes are intialized all the way up including the result node. - -var - Run: PVirtualNode; - -begin - Result := GetLastVisibleNoInit(Node, ConsiderChildrenAbove); - - Run := Result; - while Assigned(Run) and (Run <> Node) and (Run <> RootNode) do - begin - if not (vsInitialized in Run.States) then - InitNode(Run); - Run := Run.Parent; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetLastVisibleChild(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - -// Determines the last visible child of the given node and initializes it if necessary. - -begin - if (Node = nil) or (Node = FRoot) then - Result := GetLastChild(FRoot) - else - if FullyVisible[Node] and (vsExpanded in Node.States) then - Result := GetLastChild(Node) - else - Result := nil; - - if Assigned(Result) and (not (vsVisible in Result.States) or - (not IncludeFiltered and IsEffectivelyFiltered[Result])) then - Result := GetPreviousVisibleSibling(Result, IncludeFiltered); - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetLastVisibleChildNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - -// Determines the last visible child of the given node without initialization. - -begin - if (Node = nil) or (Node = FRoot) then - Result := GetLastChildNoInit(FRoot) - else - if FullyVisible[Node] and (vsExpanded in Node.States) then - Result := GetLastChildNoInit(Node) - else - Result := nil; - - if Assigned(Result) and (not (vsVisible in Result.States) or - (not IncludeFiltered and IsEffectivelyFiltered[Result])) then - Result := GetPreviousVisibleSiblingNoInit(Result, IncludeFiltered); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetLastVisibleNoInit(Node: PVirtualNode = nil; - ConsiderChildrenAbove: Boolean = True; IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the very last visible node in the tree while optionally considering toChildrenAbove. -// No initialization is performed. -var - Next: PVirtualNode; - -begin - Result := GetLastVisibleChildNoInit(Node, IncludeFiltered); - if not ConsiderChildrenAbove or not (toChildrenAbove in FOptions.PaintOptions) then - while Assigned(Result) and (vsExpanded in Result.States) do - begin - // Test if there is a next last child. If not keep the node from the last run. - // Otherwise use the next last child. - Next := GetLastChildNoInit(Result); - if Assigned(Next) and (not (vsVisible in Next.States) or - (not IncludeFiltered and IsEffectivelyFiltered[Next])) then - Next := GetPreviousVisibleSiblingNoInit(Next, IncludeFiltered); - if Next = nil then - Break; - Result := Next; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetMaxColumnWidth(Column: TColumnIndex; UseSmartColumnWidth: Boolean = False): Integer; - -// This method determines the width of the largest node in the given column. -// If UseSmartColumnWidth is True then only the visible nodes which are in view will be considered -// Note: If UseSmartColumnWidth is False then every visible node in the tree will be initialized contradicting so -// the virtual paradigm. - -var - Run, - LastNode, - NextNode: PVirtualNode; - TextLeft, - CurrentWidth: Integer; - lOffsets: TVTOffsets; -begin - if OperationCanceled then - begin - // Behave non-destructive. - Result := FHeader.Columns[Column].Width; - Exit; - end - else - Result := 0; - - StartOperation(okGetMaxColumnWidth); - try - if Assigned(FOnBeforeGetMaxColumnWidth) then - FOnBeforeGetMaxColumnWidth(FHeader, Column, UseSmartColumnWidth); - - if UseSmartColumnWidth then // Get first visible node which is in view. - Run := GetTopNode - else - Run := GetFirstVisible(nil, True); - - // Decide where to stop. - if UseSmartColumnWidth then - LastNode := GetNextVisible(BottomNode) - else - LastNode := nil; - - if hoAutoResizeInclCaption in FHeader.Options then - Result := Result + (2 * Header.Columns[Column].Margin + Header.Columns[Column].CaptionWidth + 2); - - while Assigned(Run) and not OperationCanceled do - begin - GetOffsets(Run, lOffsets, TVTElement.ofsLabel, Column); - TextLeft := lOffsets[TVTElement.ofsLabel]; - CurrentWidth := DoGetNodeWidth(Run, Column); - Inc(CurrentWidth, DoGetNodeExtraWidth(Run, Column)); - Inc(CurrentWidth, DoGetCellContentMargin(Run, Column).X); - - // Background for fix: - // DoGetNodeWidth works correctly to return just the - // headerwidth in vsMultiline state of the node. But the - // following code was adding TextLeft unnecessarily. This - // caused a width increase each time a column splitter - // was double-clicked for the option hoDblClickResize that - // really does not apply for vsMultiline case. - // Fix: If the node is multiline, leave the current width as - // it is as returned by DoGetNodeWidth logic above. - if (Column > NoColumn) and (vsMultiline in Run.States) then - Result := CurrentWidth - else - if Result < (TextLeft + CurrentWidth) then - Result := TextLeft + CurrentWidth; - - // Get next visible node and update left node position if needed. - NextNode := GetNextVisible(Run, True); - if NextNode = LastNode then - Break; - Run := NextNode; - end; - if toShowVertGridLines in FOptions.PaintOptions then - Inc(Result); - - if Assigned(FOnAfterGetMaxColumnWidth) then - FOnAfterGetMaxColumnWidth(FHeader, Column, Result); - - finally - EndOperation(okGetMaxColumnWidth); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNext(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns next node in tree while optionally considering toChildrenAbove. The Result will be initialized if needed. - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - // If this node has no siblings use the parent. - if not Assigned(Result.NextSibling) then - begin - Result := Result.Parent; - if Result = FRoot then - begin - Result := nil; - end; - end - else - begin - // There is at least one sibling so take it. - Result := Result.NextSibling; - - // Has this node got children? Initialize them if necessary. - if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then - InitChildren(Result); - - // Now take a look at the children. - while Assigned(Result.FirstChild) do - begin - Result := Result.FirstChild; - if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then - InitChildren(Result); - end; - end; - end - else - begin - // Has this node got children? - if vsHasChildren in Result.States then - begin - // Yes, there are child nodes. Initialize them if necessary. - if Result.ChildCount = 0 then - InitChildren(Result); - end; - - // if there is no child node try siblings - if Assigned(Result.FirstChild) then - Result := Result.FirstChild - else - begin - repeat - // Is there a next sibling? - if Assigned(Result.NextSibling) then - begin - Result := Result.NextSibling; - Break; - end - else - begin - // No sibling anymore, so use the parent's next sibling. - if Result.Parent <> FRoot then - Result := Result.Parent - else - begin - // There are no further nodes to examine, hence there is no further visible node. - Result := nil; - Break; - end; - end; - until False; - end; - end; - end; - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal; - ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -begin - if (Node = nil) or (Node = FRoot) then - Result := GetFirstNoInit(ConsiderChildrenAbove) - else - Result := GetNextNoInit(Node, ConsiderChildrenAbove); - - while Assigned(Result) and (GetCheckState(Result) <> State) do - Result := GetNextNoInit(Result, ConsiderChildrenAbove); - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextChecked(Node: PVirtualNode; ConsiderChildrenAbove: Boolean): PVirtualNode; -begin - Result := Self.GetNextChecked(Node, csCheckedNormal, ConsiderChildrenAbove); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the next node in the tree which is currently marked for a clipboard operation. Since only visible nodes can -// be marked (or they are hidden after they have been marked) it is not necessary to initialize nodes to check for -// child nodes. The result, however, is initialized if necessary. - -begin - if ClipboardStates * FStates <> [] then - begin - if (Node = nil) or (Node = FRoot) then - Result := GetFirstNoInit(ConsiderChildrenAbove) - else - Result := GetNextNoInit(Node, ConsiderChildrenAbove); - while Assigned(Result) and not (vsCutOrCopy in Result.States) do - Result := GetNextNoInit(Result, ConsiderChildrenAbove); - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); - end - else - Result := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the next node in tree which is initialized. - -begin - Result := Node; - repeat - Result := GetNextNoInit(Result, ConsiderChildrenAbove); - until (Result = nil) or (vsInitialized in Result.States); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextLeaf(Node: PVirtualNode): PVirtualNode; - -// Returns the next node in the tree which has currently no children. -// The result is initialized if necessary. - -begin - if (Node = nil) or (Node = FRoot) then - Result := FRoot.FirstChild - else - Result := GetNext(Node); - while Assigned(Result) and (vsHasChildren in Result.States) do - Result := GetNext(Result); - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode; - -// Returns the next node in the tree on a specific level. -// The result is initialized if necessary. - -var - StartNodeLevel: Cardinal; - -begin - Result := nil; - - if Assigned(Node) and (Node <> FRoot) then - begin - StartNodeLevel := GetNodeLevel(Node); - - if StartNodeLevel < NodeLevel then - begin - Result := GetNext(Node); - if Assigned(Result) and (GetNodeLevel(Result) <> NodeLevel) then - Result := GetNextLevel(Result, NodeLevel); - end - else - if StartNodeLevel = NodeLevel then - begin - Result := Node.NextSibling; - if not Assigned(Result) then // i.e. start node was a last sibling - begin - Result := Node.Parent; - if Assigned(Result) then - begin - // go to next anchestor of the start node which has a next sibling (if exists) - while Assigned(Result) and not Assigned(Result.NextSibling) do - Result := Result.Parent; - if Assigned(Result) then - Result := GetNextLevel(Result.NextSibling, NodeLevel); - end; - end; - end - else - // i.e. StartNodeLevel > NodeLevel - Result := GetNextLevel(Node.Parent, NodeLevel); - end; - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean): PVirtualNode; - -// Optimized version of GetNext performing no initialization, but optionally considering toChildrenAbove. - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - // If this node has no siblings use the parent. - if not Assigned(Result.NextSibling) then - begin - Result := Result.Parent; - if Result = FRoot then - begin - Result := nil; - end; - end - else - begin - // There is at least one sibling so take it. - Result := Result.NextSibling; - - // Now take a look at the children. - while Assigned(Result.FirstChild) do - begin - Result := Result.FirstChild; - end; - end; - end - else - begin - // If there is no child node try siblings. - if Assigned(Result.FirstChild) then - Result := Result.FirstChild - else - begin - repeat - // Is there a next sibling? - if Assigned(Result.NextSibling) then - begin - Result := Result.NextSibling; - Break; - end - else - begin - // No sibling anymore, so use the parent's next sibling. - if Result.Parent <> FRoot then - Result := Result.Parent - else - begin - // There are no further nodes to examine, hence there is no further visible node. - Result := nil; - Break; - end; - end; - until False; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the next node in the tree which is currently selected. Since children of unitialized nodes cannot be -// in the current selection (because they simply do not exist yet) it is not necessary to initialize nodes here. -// The result however is initialized if necessary. - -begin - if FSelectionCount > 0 then - begin - if (Node = nil) or (Node = FRoot) then - Result := GetFirstNoInit(ConsiderChildrenAbove) - else - Result := GetNextNoInit(Node, ConsiderChildrenAbove); - while Assigned(Result) and not (vsSelected in Result.States) do - Result := GetNextNoInit(Result, ConsiderChildrenAbove); - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); - end - else - Result := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextSibling(Node: PVirtualNode): PVirtualNode; - -// Returns the next sibling of Node and initializes it if necessary. - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - Result := Result.NextSibling; - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); - end; -end; - -function TBaseVirtualTree.GetNextSiblingNoInit(Node: PVirtualNode): PVirtualNode; - -// Returns the next sibling of Node. - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - Result := Result.NextSibling; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; - -// Returns next node in tree, with regard to Node, which is visible. -// Nodes which need an initialization (including the result) are initialized. -// toChildrenAbove is optionally considered which is the default here. - -var - ForceSearch: Boolean; - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - repeat - // If the given node is not visible then look for a parent node which is visible, otherwise we will - // likely go unnecessarily through a whole bunch of invisible nodes. - if not FullyVisible[Result] then - Result := GetVisibleParent(Result, True); - - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - repeat - // If there a no siblings anymore, go up one level. - if not Assigned(Result.NextSibling) then - begin - Result := Result.Parent; - if Result = FRoot then - begin - Result := nil; - Break; - end; - - if not (vsInitialized in Result.States) then - InitNode(Result); - if vsVisible in Result.States then - Break; - end - else - begin - // There is at least one sibling so take it. - Result := Result.NextSibling; - if not (vsInitialized in Result.States) then - InitNode(Result); - if not (vsVisible in Result.States) then - Continue; - - // Now take a look at the children. - // As the children are initialized while toggling, we don't need to do this here. - while (vsExpanded in Result.States) and Assigned(Result.FirstChild) do - begin - Result := Result.FirstChild; - if not (vsInitialized in Result.States) then - InitNode(Result); - if not (vsVisible in Result.States) then - Break; - end; - - // If we found a visible node we don't need to search any longer. - if vsVisible in Result.States then - Break; - end; - until False; - end - else - begin - // Has this node got children? - if [vsHasChildren, vsExpanded] * Result.States = [vsHasChildren, vsExpanded] then - begin - // Yes, there are child nodes. Initialize them if necessary. - if Result.ChildCount = 0 then - InitChildren(Result); - end; - - // Child nodes are the first choice if possible. - if (vsExpanded in Result.States) and Assigned(Result.FirstChild) then - begin - Result := GetFirstChild(Result); - ForceSearch := False; - end - else - ForceSearch := True; - - // If there are no children or the first child is not visible then search the sibling nodes or traverse parents. - if Assigned(Result) and (ForceSearch or not (vsVisible in Result.States)) then - begin - repeat - // Is there a next sibling? - if Assigned(Result.NextSibling) then - begin - Result := Result.NextSibling; - if not (vsInitialized in Result.States) then - InitNode(Result); - if vsVisible in Result.States then - Break; - end - else - begin - // No sibling anymore, so use the parent's next sibling. - if Result.Parent <> FRoot then - Result := Result.Parent - else - begin - // There are no further nodes to examine, hence there is no further visible node. - Result := nil; - Break; - end; - end; - until False; - end; - end; - until not Assigned(Result) or IsEffectivelyVisible[Result]; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextVisibleNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; - -// Returns the next node in tree, with regard to Node, which is visible. -// toChildrenAbove is optionally considered (which is the default). No initialization is done. - -var - ForceSearch: Boolean; - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - repeat - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - repeat - // If there a no siblings anymore, go up one level. - if not Assigned(Result.NextSibling) then - begin - Result := Result.Parent; - if Result = FRoot then - begin - Result := nil; - Break; - end; - if vsVisible in Result.States then - Break; - end - else - begin - // There is at least one sibling so take it. - Result := Result.NextSibling; - if not (vsVisible in Result.States) then - Continue; - - // Now take a look at the children. - while (vsExpanded in Result.States) and Assigned(Result.FirstChild) do - begin - Result := Result.FirstChild; - if not (vsVisible in Result.States) then - Break; - end; - - // If we found a visible node we don't need to search any longer. - if vsVisible in Result.States then - Break; - end; - until False; - end - else - begin - // If the given node is not visible then look for a parent node which is visible, otherwise we will - // likely go unnecessarily through a whole bunch of invisible nodes. - if not FullyVisible[Result] then - Result := GetVisibleParent(Result, True); - - // Child nodes are the first choice if possible. - if (vsExpanded in Result.States) and Assigned(Result.FirstChild) then - begin - Result := Result.FirstChild; - ForceSearch := False; - end - else - ForceSearch := True; - - // If there are no children or the first child is not visible then search the sibling nodes or traverse parents. - if ForceSearch or not (vsVisible in Result.States) then - begin - repeat - // Is there a next sibling? - if Assigned(Result.NextSibling) then - begin - Result := Result.NextSibling; - if vsVisible in Result.States then - Break; - end - else - begin - // No sibling anymore, so use the parent's next sibling. - if Result.Parent <> FRoot then - Result := Result.Parent - else - begin - // There are no further nodes to examine, hence there is no further visible node. - Result := nil; - Break; - end; - end; - until False; - end; - end; - until not Assigned(Result) or IsEffectivelyVisible[Result]; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the next visible sibling after Node. Initialization is done implicitly. - -begin - Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); - - Result := Node; - repeat - Result := GetNextSibling(Result); - until not Assigned(Result) or ((vsVisible in Result.States) and - (IncludeFiltered or not IsEffectivelyFiltered[Result])); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNextVisibleSiblingNoInit(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the next visible sibling after Node. - -begin - Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); - - Result := Node; - repeat - Result := Result.NextSibling; - until not Assigned(Result) or ((vsVisible in Result.States) and - (IncludeFiltered or not IsEffectivelyFiltered[Result])); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNodeAt(X, Y: Integer): PVirtualNode; - -// Overloaded variant of GetNodeAt to easy life of application developers which do not need to have the exact -// top position returned and always use client coordinates. - -var - Dummy: Integer; - -begin - Result := GetNodeAt(X, Y, True, Dummy); -end; - -function TBaseVirtualTree.GetNodeAt(const P: TPoint): PVirtualNode; -begin - Result := GetNodeAt(P.X, P.Y); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNodeAt(X, Y: Integer; Relative: Boolean; var NodeTop: Integer): PVirtualNode; - -// This method returns the node that occupies the specified point, or nil if there's none. -// If Releative is True then X and Y are given in client coordinates otherwise they are considered as being -// absolute values into the virtual tree image (regardless of the current offsets in the tree window). -// NodeTop gets the absolute or relative top position of the node returned or is untouched if no node -// could be found. - -var - AbsolutePos, - CurrentPos: Cardinal; - -begin - if Y < 0 then - Y := 0; - - AbsolutePos := Y; - if Relative then - Inc(AbsolutePos, -FOffsetY); - - // CurrentPos tracks a running term of the current position to test for. - // It corresponds always to the top position of the currently considered node. - CurrentPos := 0; - - // If the cache is available then use it. - if tsUseCache in FStates then - Result := FindInPositionCache(AbsolutePos, CurrentPos) - else - Result := GetFirstVisibleNoInit(nil, True); - - // Determine node, of which position and height corresponds to the scroll position most closely. - while Assigned(Result) and (Result <> FRoot) do - begin - if AbsolutePos < (CurrentPos + NodeHeight[Result]) then - Break; - Inc(CurrentPos, NodeHeight[Result]); - Result := GetNextVisibleNoInit(Result, True); - end; - - if Result = FRoot then - Result := nil; - - // Since the given vertical position is likely not the same as the top position - // of the found node this top position is returned. - if Assigned(Result) then - begin - NodeTop := CurrentPos; - if Relative then - Inc(NodeTop, FOffsetY); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - - -function TBaseVirtualTree.GetNodeData(Node: PVirtualNode): Pointer; - -// Returns the address of the user defined data area in the node. - -begin - Assert((FNodeDataSize > 0) or not Assigned(Node), 'NodeDataSize not initialized.'); - if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) then - Result := nil - else - begin - Result := @Node.Data; - Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323 - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNodeData(pNode: PVirtualNode): T; - -// Returns the associated data converted to the class given in the generic part of the function. - -var - P: Pointer; -begin - P := Self.GetNodeData(pNode); - if Assigned(P) then - Exit(T(P^)) - else - Exit(Default(T)); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetInterfaceFromNodeData(pNode: PVirtualNode): T; -begin - if Assigned(pNode) then - Result := T(Self.GetNodeData(pNode)^) - else - Result := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNodeDataAt(pXCoord, pYCoord: Integer): T; - -// Returns the associated data at the specified coordinates converted to the type given in the generic part of the function. - -var - lNode: PVirtualNode; -begin - lNode := GetNodeAt(pXCoord, pYCoord); - Result := Self.GetNodeData(lNode); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetFirstSelectedNodeData(): T; - -// Returns of the first selected node associated data converted to the type given in the generic part of the function. - -begin - Result := Self.GetNodeData(GetFirstSelected()); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetNodeLevel(Node: PVirtualNode): Cardinal; - -// returns the level of the given node - -var - Run: PVirtualNode; - -begin - Result := 0; - if Assigned(Node) and (Node <> FRoot) then - begin - Run := Node.Parent; - while Run <> FRoot do - begin - Run := Run.Parent; - Inc(Result); - end; - end; -end; - - -//---------------------------------------------------------------------------------------------------------------------- -// Function introduced to avoid spaghetti code to fix setting of FLastSelectionLevel -// at various places that now needs to avoid setting it for a disabled node -function TBaseVirtualTree.GetNodeLevelForSelectConstraint(Node: PVirtualNode): integer; -begin - if Assigned(Node) and not (vsDisabled in Node.States) then - result := GetNodeLevel(Node) - else - result := -1; -end; - - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPrevious(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns previous node in tree. If ConsiderChildrenAbove is True the function considers -// whether toChildrenAbove is currently set, otherwise the result will always be the previous -// node in top-down order regardless of the current PaintOptions. -// The Result will be initialized if needed. - -var - Run: PVirtualNode; - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - // Has this node got children? Initialize them if necessary. - if (vsHasChildren in Result.States) and (Result.ChildCount = 0) then - InitChildren(Result); - - // If there is a last child, take it; if not try the previous sibling. - if Assigned(Result.LastChild) then - Result := Result.LastChild - else - if Assigned(Result.PrevSibling) then - Result := Result.PrevSibling - else - begin - // If neither a last child nor a previous sibling exist, go the tree upwards and - // look, wether one of the parent nodes have a previous sibling. If not the result - // will ne nil. - repeat - Result := Result.Parent; - Run := nil; - if Result <> FRoot then - Run := Result.PrevSibling - else - Result := nil; - until Assigned(Run) or (Result = nil); - - if Assigned(Run) then - Result := Run; - end; - end - else - begin - // Is there a previous sibling? - if Assigned(Node.PrevSibling) then - begin - // Go down and find the last child node. - Result := GetLast(Node.PrevSibling); - if Result = nil then - Result := Node.PrevSibling; - end - else - // no previous sibling so the parent of the node is the previous visible node - if Node.Parent <> FRoot then - Result := Node.Parent - else - Result := nil; - end; - end; - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousChecked(Node: PVirtualNode; State: TCheckState = csCheckedNormal; - ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -begin - if (Node = nil) or (Node = FRoot) then - Result := GetLastNoInit(nil, ConsiderChildrenAbove) - else - Result := GetPreviousNoInit(Node, ConsiderChildrenAbove); - - while Assigned(Result) and (GetCheckState(Result) <> State) do - Result := GetPreviousNoInit(Result, ConsiderChildrenAbove); - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousCutCopy(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the previous node in the tree which is currently marked for a clipboard operation. Since only visible nodes can -// be marked (or they are hidden after they have been marked) it is not necessary to initialize nodes to check for -// child nodes. The result, however, is initialized if necessary. - -begin - if ClipboardStates * FStates <> [] then - begin - if (Node = nil) or (Node = FRoot) then - Result := GetLastNoInit(nil, ConsiderChildrenAbove) - else - Result := GetPreviousNoInit(Node, ConsiderChildrenAbove); - while Assigned(Result) and not (vsCutOrCopy in Result.States) do - Result := GetPreviousNoInit(Result, ConsiderChildrenAbove); - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); - end - else - Result := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousInitialized(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the previous node in tree which is initialized. - -begin - Result := Node; - repeat - Result := GetPreviousNoInit(Result, ConsiderChildrenAbove); - until (Result = nil) or (vsInitialized in Result.States); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousLeaf(Node: PVirtualNode): PVirtualNode; - -// Returns the previous node in the tree which has currently no children. -// The result is initialized if necessary. - -begin - if (Node = nil) or (Node = FRoot) then - Result := FRoot.LastChild - else - Result := GetPrevious(Node); - while Assigned(Result) and (vsHasChildren in Result.States) do - Result := GetPrevious(Result); - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousLevel(Node: PVirtualNode; NodeLevel: Cardinal): PVirtualNode; - -// Returns the previous node in the tree on a specific level. -// The result is initialized if necessary. - -var - StartNodeLevel: Cardinal; - Run: PVirtualNode; - -begin - Result := nil; - - if Assigned(Node) and (Node <> FRoot) then - begin - StartNodeLevel := GetNodeLevel(Node); - - if StartNodeLevel < NodeLevel then - begin - Result := Node.PrevSibling; - if Assigned(Result) then - begin - // go to last descendant of previous sibling with desired node level (if exists) - Run := Result; - while Assigned(Run) and (GetNodeLevel(Run) < NodeLevel) do - begin - Result := Run; - Run := GetLastChild(Run); - end; - if Assigned(Run) and (GetNodeLevel(Run) = NodeLevel) then - Result := Run - else - begin - if Assigned(Result.PrevSibling) then - Result := GetPreviousLevel(Result, NodeLevel) - else - if Assigned(Result) and (Result.Parent <> FRoot) then - Result := GetPreviousLevel(Result.Parent, NodeLevel) - else - Result := nil; - end; - end - else - Result := GetPreviousLevel(Node.Parent, NodeLevel); - end - else - if StartNodeLevel = NodeLevel then - begin - Result := Node.PrevSibling; - if not Assigned(Result) then // i.e. start node was a first sibling - begin - Result := Node.Parent; - if Assigned(Result) then - Result := GetPreviousLevel(Result, NodeLevel); - end; - end - else // i.e. StartNodeLevel > NodeLevel - Result := GetPreviousLevel(Node.Parent, NodeLevel); - end; - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousNoInit(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns previous node in tree, optionally considering toChildrenAbove. No initialization is performed. - -var - Run: PVirtualNode; - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - // If there is a last child, take it; if not try the previous sibling. - if Assigned(Result.LastChild) then - Result := Result.LastChild - else - if Assigned(Result.PrevSibling) then - Result := Result.PrevSibling - else - begin - // If neither a last child nor a previous sibling exist, go the tree upwards and - // look, wether one of the parent nodes have a previous sibling. If not the result - // will ne nil. - repeat - Result := Result.Parent; - Run := nil; - if Result <> FRoot then - Run := Result.PrevSibling - else - Result := nil; - until Assigned(Run) or (Result = nil); - - if Assigned(Run) then - Result := Run; - end; - end - else - begin - // Is there a previous sibling? - if Assigned(Node.PrevSibling) then - begin - // Go down and find the last child node. - Result := GetLastNoInit(Node.PrevSibling); - if Result = nil then - Result := Node.PrevSibling; - end - else - // No previous sibling so the parent of the node is the previous node. - if Node.Parent <> FRoot then - Result := Node.Parent - else - Result := nil; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousSelected(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = False): PVirtualNode; - -// Returns the previous node in the tree which is currently selected. Since children of unitialized nodes cannot be -// in the current selection (because they simply do not exist yet) it is not necessary to initialize nodes here. -// The result however is initialized if necessary. - -begin - if FSelectionCount > 0 then - begin - if (Node = nil) or (Node = FRoot) then - Result := FRoot.LastChild - else - Result := GetPreviousNoInit(Node, ConsiderChildrenAbove); - while Assigned(Result) and not (vsSelected in Result.States) do - Result := GetPreviousNoInit(Result, ConsiderChildrenAbove); - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); - end - else - Result := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousSibling(Node: PVirtualNode): PVirtualNode; - -// Returns the previous sibling of Node and initializes it if necessary. - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - Result := Result.PrevSibling; - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); - end; -end; - -function TBaseVirtualTree.GetPreviousSiblingNoInit(Node: PVirtualNode): PVirtualNode; - -// Returns the previous sibling of Node - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - Result := Result.PrevSibling; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousVisible(Node: PVirtualNode; ConsiderChildrenAbove: Boolean = True): PVirtualNode; - -// Returns the previous node in tree, with regard to Node, which is visible. -// Nodes which need an initialization (including the result) are initialized. -// toChildrenAbove is optionally considered which is the default here. - -var - Marker: PVirtualNode; - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - repeat - // If the given node is not visible then look for a parent node which is visible and use its last visible - // child or the parent node (if there is no visible child) as result. - if not FullyVisible[Result] then - begin - Result := GetVisibleParent(Result, True); - if Result = FRoot then - Result := nil; - Marker := GetLastVisible(Result, True); - if Assigned(Marker) then - Result := Marker; - end - else - begin - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - repeat - if Assigned(Result.LastChild) and (vsExpanded in Result.States) then - begin - Result := Result.LastChild; - if not (vsInitialized in Result.States) then - InitNode(Result); - - if vsVisible in Result.States then - Break; - end - else - if Assigned(Result.PrevSibling) then - begin - if not (vsInitialized in Result.PrevSibling.States) then - InitNode(Result.PrevSibling); - - if vsVisible in Result.PrevSibling.States then - begin - Result := Result.PrevSibling; - Break; - end; - end - else - begin - Marker := nil; - repeat - Result := Result.Parent; - if Result <> FRoot then - Marker := GetPreviousVisibleSibling(Result, True) - else - Result := nil; - until Assigned(Marker) or (Result = nil); - if Assigned(Marker) then - Result := Marker; - - Break; - end; - until False; - end - else - begin - repeat - // Is there a previous sibling node? - if Assigned(Result.PrevSibling) then - begin - Result := Result.PrevSibling; - // Initialize the new node and check its visibility. - if not (vsInitialized in Result.States) then - InitNode(Result); - if vsVisible in Result.States then - begin - // If there are visible child nodes then use the last one. - Marker := GetLastVisible(Result, True, True); - if Assigned(Marker) then - Result := Marker; - Break; - end; - end - else - begin - // No previous sibling there so the parent node is the nearest previous node. - Result := Result.Parent; - if Result = FRoot then - Result := nil; - Break; - end; - until False; - end; - - if Assigned(Result) and not (vsInitialized in Result.States) then - InitNode(Result); - end; - until not Assigned(Result) or IsEffectivelyVisible[Result]; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousVisibleNoInit(Node: PVirtualNode; - ConsiderChildrenAbove: Boolean = True): PVirtualNode; - -// Returns the previous node in tree, with regard to Node, which is visible. -// toChildrenAbove is optionally considered which is the default here. - -var - Marker: PVirtualNode; - -begin - Result := Node; - if Assigned(Result) then - begin - Assert(Result <> FRoot, 'Node must not be the hidden root node.'); - - repeat - // If the given node is not visible then look for a parent node which is visible and use its last visible - // child or the parent node (if there is no visible child) as result. - if not FullyVisible[Result] then - begin - Result := GetVisibleParent(Result, True); - if Result = FRoot then - Result := nil; - Marker := GetLastVisibleNoInit(Result, True); - if Assigned(Marker) then - Result := Marker; - end - else - begin - if ConsiderChildrenAbove and (toChildrenAbove in FOptions.PaintOptions) then - begin - repeat - // Is the current node expanded and has children? - if (vsExpanded in Result.States) and Assigned(Result.LastChild) then - begin - Result := Result.LastChild; - if vsVisible in Result.States then - Break; - end - else - if Assigned(Result.PrevSibling) then - begin - // No children anymore, so take the previous sibling. - Result := Result.PrevSibling; - if vsVisible in Result.States then - Break; - end - else - begin - // No children and no previous siblings, so walk up the tree and look wether - // a parent has a previous visible sibling. If that is the case take it, - // otherwise there is no previous visible node. - Marker := nil; - repeat - Result := Result.Parent; - if Result <> FRoot then - Marker := GetPreviousVisibleSiblingNoInit(Result, True) - else - Result := nil; - until Assigned(Marker) or (Result = nil); - if Assigned(Marker) then - Result := Marker; - Break; - end; - until False; - end - else - begin - repeat - // Is there a previous sibling node? - if Assigned(Result.PrevSibling) then - begin - Result := Result.PrevSibling; - if vsVisible in Result.States then - begin - // If there are visible child nodes then use the last one. - Marker := GetLastVisibleNoInit(Result, True, True); - if Assigned(Marker) then - Result := Marker; - Break; - end; - end - else - begin - // No previous sibling there so the parent node is the nearest previous node. - Result := Result.Parent; - if Result = FRoot then - Result := nil; - Break; - end; - until False; - end; - end; - until not Assigned(Result) or IsEffectivelyVisible[Result]; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousVisibleSibling(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the previous visible sibling before Node. Initialization is done implicitly. - -begin - Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); - - Result := Node; - repeat - Result := GetPreviousSibling(Result); - until not Assigned(Result) or ((vsVisible in Result.States) and - (IncludeFiltered or not IsEffectivelyFiltered[Result])); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetPreviousVisibleSiblingNoInit(Node: PVirtualNode; - IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the previous visible sibling before Node. - -begin - Assert(Assigned(Node) and (Node <> FRoot), 'Invalid parameter.'); - - Result := Node; - repeat - Result := Result.PrevSibling; - until not Assigned(Result) or ((vsVisible in Result.States) and - (IncludeFiltered or not IsEffectivelyFiltered[Result])); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.Nodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; - -// Enumeration for all nodes - -begin - Result.FMode := vneAll; - Result.FTree := Self; - Result.FConsiderChildrenAbove := ConsiderChildrenAbove; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CheckedNodes(State: TCheckState; ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; - -// Enumeration for all checked nodes - -begin - Result.FMode := vneChecked; - Result.FTree := Self; - Result.FState := State; - Result.FConsiderChildrenAbove := ConsiderChildrenAbove; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ChildNodes(Node: PVirtualNode): TVTVirtualNodeEnumeration; - -// Enumeration for child nodes - -begin - Result.FMode := vneChild; - Result.FTree := Self; - Result.FNode := Node; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.CutCopyNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; - -// Enumeration for cut copy node - -begin - Result.FMode := vneCutCopy; - Result.FTree := Self; - Result.FConsiderChildrenAbove := ConsiderChildrenAbove; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.InitializedNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; - -// Enumeration for initialized nodes - -begin - Result.FMode := vneInitialized; - Result.FTree := Self; - Result.FConsiderChildrenAbove := ConsiderChildrenAbove; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.LeafNodes: TVTVirtualNodeEnumeration; - -// Enumeration for leaf nodes - -begin - Result.FMode := vneLeaf; - Result.FTree := Self; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.LevelNodes(NodeLevel: Cardinal): TVTVirtualNodeEnumeration; - -// Enumeration for level nodes - -begin - Result.FMode := vneLevel; - Result.FTree := Self; - Result.FNodeLevel := NodeLevel; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.NoInitNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; - -// Enumeration for no init nodes -begin - Result.FMode := vneNoInit; - Result.FTree := Self; - Result.FConsiderChildrenAbove := ConsiderChildrenAbove; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.SelectedNodes(ConsiderChildrenAbove: Boolean): TVTVirtualNodeEnumeration; - -// Enumeration for selected nodes - -begin - Result.FMode := vneSelected; - Result.FTree := Self; - Result.FConsiderChildrenAbove := ConsiderChildrenAbove; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.VisibleNodes(Node: PVirtualNode; ConsiderChildrenAbove: Boolean; - IncludeFiltered: Boolean): TVTVirtualNodeEnumeration; - -// Enumeration for visible nodes - -begin - Result.FMode := vneVisible; - Result.FTree := Self; - Result.FNode := Node; - Result.FConsiderChildrenAbove := ConsiderChildrenAbove; - Result.FIncludeFiltered := IncludeFiltered; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.VisibleChildNodes(Node: PVirtualNode; IncludeFiltered: Boolean): TVTVirtualNodeEnumeration; - -// Enumeration for visible child nodes - -begin - Result.FMode := vneVisibleChild; - Result.FTree := Self; - Result.FNode := Node; - Result.FIncludeFiltered := IncludeFiltered; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.VisibleChildNoInitNodes(Node: PVirtualNode; IncludeFiltered: Boolean): TVTVirtualNodeEnumeration; - -// Enumeration for visible child no init nodes - -begin - Result.FMode := vneVisibleNoInitChild; - Result.FTree := Self; - Result.FNode := Node; - Result.FIncludeFiltered := IncludeFiltered; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.VisibleNoInitNodes(Node: PVirtualNode; ConsiderChildrenAbove: Boolean; - IncludeFiltered: Boolean): TVTVirtualNodeEnumeration; - -// Enumeration for visible no init nodes - -begin - Result.FMode := vneVisibleNoInit; - Result.FTree := Self; - Result.FNode := Node; - Result.FConsiderChildrenAbove := ConsiderChildrenAbove; - Result.FIncludeFiltered := IncludeFiltered; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetSortedCutCopySet(Resolve: Boolean): TNodeArray; - -// Same as GetSortedSelection but with nodes marked as being part in the current cut/copy set (e.g. for clipboard). - -var - Run: PVirtualNode; - Counter: Cardinal; - - //--------------- local function -------------------------------------------- - - procedure IncludeThisNode(Node: PVirtualNode); - - // adds the given node to the result - - var - Len: Cardinal; - - begin - Len := Length(Result); - if Counter = Len then - begin - if Len < 100 then - Len := 100 - else - Len := Len + Len div 10; - SetLength(Result, Len); - end; - Result[Counter] := Node; - Inc(Counter); - end; - - //--------------- end local function ---------------------------------------- - -begin - Run := FRoot.FirstChild; - Counter := 0; - if Resolve then - begin - // Resolving is actually easy: just find the first cutted node in logical order - // and then never go deeper in level than this node as long as there's a sibling node. - // Restart the search for a cutted node (at any level) if there are no further siblings. - while Assigned(Run) do - begin - if vsCutOrCopy in Run.States then - begin - IncludeThisNode(Run); - if Assigned(Run.NextSibling) then - Run := Run.NextSibling - else - begin - // If there are no further siblings then go up one or more levels until a node is - // found or all nodes have been processed. Although we consider here only initialized - // nodes we don't need to make any special checks as only initialized nodes can also be selected. - repeat - Run := Run.Parent; - until (Run = FRoot) or Assigned(Run.NextSibling); - if Run = FRoot then - Break - else - Run := Run.NextSibling; - end; - end - else - Run := GetNextNoInit(Run); - end; - end - else - while Assigned(Run) do - begin - if vsCutOrCopy in Run.States then - IncludeThisNode(Run); - Run := GetNextNoInit(Run); - end; - - // set the resulting array to its real length - SetLength(Result, Counter); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetSortedSelection(Resolve: Boolean): TNodeArray; - -// Returns a list of selected nodes sorted in logical order, that is, as they appear in the tree. -// If Resolve is True then nodes which are children of other selected nodes are not put into the new array. -// This feature is in particuar important when doing drag'n drop as in this case all selected node plus their children -// need to be considered. A selected node which is child (grand child etc.) of another selected node is then -// automatically included and doesn't need to be explicitely mentioned in the returned selection array. -// -// Note: The caller is responsible for freeing the array. Allocation is done here. Usually, though, freeing the array -// doesn't need additional attention as it is automatically freed by Delphi when it gets out of scope. - -var - Run: PVirtualNode; - Counter: Cardinal; - -begin - SetLength(Result, FSelectionCount); - if FSelectionCount > 0 then - begin - Run := FRoot.FirstChild; - Counter := 0; - if Resolve then - begin - // Resolving is actually easy: just find the first selected node in logical order - // and then never go deeper in level than this node as long as there's a sibling node. - // Restart the search for a selected node (at any level) if there are no further siblings. - while Assigned(Run) do - begin - if vsSelected in Run.States then - begin - Result[Counter] := Run; - Inc(Counter); - if Assigned(Run.NextSibling) then - Run := Run.NextSibling - else - begin - // If there are no further siblings then go up one or more levels until a node is - // found or all nodes have been processed. Although we consider here only initialized - // nodes we don't need to make any special checks as only initialized nodes can also be selected. - repeat - Run := Run.Parent; - until (Run = FRoot) or Assigned(Run.NextSibling); - if Run = FRoot then - Break - else - Run := Run.NextSibling; - end; - end - else - Run := GetNextNoInit(Run); - end; - end - else - while Assigned(Run) do - begin - if vsSelected in Run.States then - begin - Result[Counter] := Run; - Inc(Counter); - end; - Run := GetNextNoInit(Run); - end; - - // Since we may have skipped some nodes the result array is likely to be smaller than the - // selection array, hence shorten the result to true length. - if Integer(Counter) < Length(Result) then - SetLength(Result, Counter); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.GetTextInfo(Node: PVirtualNode; Column: TColumnIndex; const AFont: TFont; var R: TRect; - var Text: string); - -// Generic base method for editors, hint windows etc. to get some info about a node. - -begin - R := Rect(0, 0, 0, 0); - Text := ''; - if Assigned(Font) then // 1 EConvertError due to Font being nil seen here in 01/2019, See issue #878 - AFont.Assign(Font); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetTreeRect: TRect; - -// Returns the true size of the tree in pixels. This size is at least ClientHeight x ClientWidth and depends on -// the expand state, header size etc. -// Note: if no columns are used then the width of the tree is determined by the largest node which is currently in the -// client area. This might however not be the largest node in the entire tree. - -begin - Result := Rect(0, 0, Max(FRangeX, ClientWidth), Max(FRangeY, ClientHeight)); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.GetVisibleParent(Node: PVirtualNode; IncludeFiltered: Boolean = False): PVirtualNode; - -// Returns the first (nearest) parent node of Node which is visible. -// This method is one of the seldom cases where the hidden root node could be returned. - -begin - Assert(Assigned(Node), 'Node must not be nil.'); - Assert(Node <> FRoot, 'Node must not be the hidden root node.'); - - Result := Node.Parent; - while (Result <> FRoot) and (not FullyVisible[Result] or (not IncludeFiltered and IsEffectivelyFiltered[Result])) do - Result := Result.Parent; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.HasAsParent(Node, PotentialParent: PVirtualNode): Boolean; - -// Determines whether Node has got PotentialParent as one of its parents. - -var - Run: PVirtualNode; - -begin - Result := Assigned(Node) and Assigned(PotentialParent) and (Node <> PotentialParent); - if Result then - begin - Run := Node; - while (Run <> FRoot) and (Run <> PotentialParent) do - Run := Run.Parent; - Result := Run = PotentialParent; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.InsertNode(Node: PVirtualNode; Mode: TVTNodeAttachMode; UserData: Pointer = nil): PVirtualNode; - -// Adds a new node relative to Node. The final position is determined by Mode. -// UserData can be used to set the first SizeOf(Pointer) bytes of the user data area to an initial value which can be used -// in OnInitNode and will also cause to trigger the OnFreeNode event (if <> nil) even if the node is not yet -// "officially" initialized. -// InsertNode is a compatibility method and will implicitly validate the given node if the new node -// is to be added as child node. This is however against the virtual paradigm and hence I dissuade from its usage. - -begin - if Mode <> amNoWhere then - begin - CancelEditNode; - - if Node = nil then - Node := FRoot; - // we need a new node... - Result := MakeNewNode; - // avoid erronous attach modes - if Node = FRoot then - begin - case Mode of - amInsertBefore: - Mode := amAddChildFirst; - amInsertAfter: - Mode := amAddChildLast; - end; - end; - - // Validate given node in case the new node becomes its child. - if (Mode in [amAddChildFirst, amAddChildLast]) and not (vsInitialized in Node.States) then - InitNode(Node); - InternalConnectNode(Result, Node, Self, Mode); - - // Check if there is initial user data and there is also enough user data space allocated. - if Assigned(UserData) then - SetNodeData(Result, UserData); - - if FUpdateCount = 0 then - begin - case Mode of - amInsertBefore, - amInsertAfter: - begin - // Here no initialization is necessary because *if* a node has already got children then it - // must also be initialized. - // Note: Node can never be FRoot at this point. - StructureChange(Result, crNodeAdded); - // If auto sort is enabled then sort the node or its parent (depending on the insert mode). - if (toAutoSort in FOptions.AutoOptions) and (FHeader.SortColumn > InvalidColumn) then - Sort(Node.Parent, FHeader.SortColumn, FHeader.SortDirection, True); - InvalidateToBottom(Result) - end; - amAddChildFirst, - amAddChildLast: - begin - StructureChange(Node, crChildAdded); - // If auto sort is enabled then sort the node or its parent (depending on the insert mode). - if (toAutoSort in FOptions.AutoOptions) and (FHeader.SortColumn > InvalidColumn) then - Sort(Node, FHeader.SortColumn, FHeader.SortDirection, True); - InvalidateToBottom(Node); - end; - end; - InvalidateCache(); - UpdateScrollBars(True); - end; - end - else - Result := nil; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InvalidateChildren(Node: PVirtualNode; Recursive: Boolean); - -// Invalidates Node and its immediate children. -// If Recursive is True then all grandchildren are invalidated as well. -// The node itself is initialized if necessary and its child nodes are created (and initialized too if -// Recursive is True). - -var - Run: PVirtualNode; - -begin - if Assigned(Node) then - begin - if not (vsInitialized in Node.States) then - InitNode(Node); - InvalidateNode(Node); - if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then - InitChildren(Node); - Run := Node.FirstChild; - end - else - Run := FRoot.FirstChild; - - while Assigned(Run) do - begin - InvalidateNode(Run); - if Recursive then - InvalidateChildren(Run, True); - Run := Run.NextSibling; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InvalidateColumn(Column: TColumnIndex); - -// Invalidates the client area part of a column. - -var - R: TRect; - -begin - if (FUpdateCount = 0) and HandleAllocated and FHeader.Columns.IsValidColumn(Column) then - begin - R := ClientRect; - FHeader.Columns.GetColumnBounds(Column, R.Left, R.Right); - InvalidateRect(Handle, @R, False); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.InvalidateNode(Node: PVirtualNode): TRect; - -// Initiates repaint of the given node and returns the just invalidated rectangle. - -begin - Assert(Assigned(Node), 'Node must not be nil.'); - Assert(GetCurrentThreadId = MainThreadId, 'UI controls may only be chnaged in UI thread.'); - // Reset height measured flag too to cause a re-issue of the OnMeasureItem event. - Exclude(Node.States, vsHeightMeasured); - if (FUpdateCount = 0) and HandleAllocated then - begin - Result := GetDisplayRect(Node, NoColumn, False); - InvalidateRect(Handle, @Result, False); - end - else - result := Rect(-1,-1,-1,-1); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InvalidateToBottom(Node: PVirtualNode); - -// Initiates repaint of client area starting at given node. If this node is not visible or not yet initialized -// then nothing happens. - -var - R: TRect; - -begin - if (FUpdateCount = 0) and HandleAllocated then - begin - if (Node = nil) or (Node = FRoot) then - Invalidate - else - if (vsInitialized in Node.States) and IsEffectivelyVisible[Node] then - begin - R := GetDisplayRect(Node, NoColumn, False); - if R.Top < ClientHeight then - begin - if (toChildrenAbove in FOptions.PaintOptions) and (vsExpanded in Node.States) then - Dec(R.Top, Node.TotalHeight + NodeHeight[Node]); - R.Bottom := ClientHeight; - InvalidateRect(Handle, @R, False); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.InvertSelection(VisibleOnly: Boolean); - -// Inverts the current selection (so nodes which are selected become unselected and vice versa). -// If VisibleOnly is True then only visible nodes are considered. - -var - Run: PVirtualNode; - NewSize: Integer; - NextFunction: TGetNextNodeProc; - TriggerChange: Boolean; - -begin - if not FSelectionLocked and (toMultiSelect in FOptions.SelectionOptions) then - begin - Run := FRoot.FirstChild; - ClearTempCache; - if VisibleOnly then - NextFunction := GetNextVisibleNoInit - else - NextFunction := GetNextNoInit; - while Assigned(Run) do - begin - if vsSelected in Run.States then - InternalRemoveFromSelection(Run) - else - InternalCacheNode(Run); - Run := NextFunction(Run); - end; - - // do some housekeeping - // Need to trigger the OnChange event from here if nodes were only deleted but not added. - TriggerChange := False; - NewSize := PackArray(FSelection, FSelectionCount); - if NewSize > -1 then - begin - FSelectionCount := NewSize; - SetLength(FSelection, FSelectionCount); - TriggerChange := True; - end; - if FTempNodeCount > 0 then - begin - AddToSelection(FTempNodeCache, FTempNodeCount); - ClearTempCache; - TriggerChange := False; - end; - Invalidate; - if TriggerChange then - Change(nil); - if Self.SelectedCount = 0 then - FNextNodeToSelect := nil;//Ensure that no other node is selected now - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.IsEditing: Boolean; - -begin - Result := tsEditing in FStates; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.IsMouseSelecting: Boolean; - -begin - Result := (tsDrawSelPending in FStates) or (tsDrawSelecting in FStates); -end; - -function TBaseVirtualTree.IsUpdating: Boolean; -// The tree does currently not update its window because a BeginUpdate has not yet ended. -begin - Exit(UpdateCount > 0); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.IterateSubtree(Node: PVirtualNode; Callback: TVTGetNodeProc; Data: Pointer; - Filter: TVirtualNodeStates = []; DoInit: Boolean = False; ChildNodesOnly: Boolean = False): PVirtualNode; - -// Iterates through the all children and grandchildren etc. of Node (or the entire tree if Node = nil) -// and calls for each node the provided callback method (which must not be empty). -// Filter determines which nodes to consider (an empty set denotes all nodes). -// If DoInit is True then nodes which aren't initialized yet will be initialized. -// Note: During execution of the callback the application can set Abort to True. In this case the iteration is stopped -// and the last accessed node (the one on which the callback set Abort to True) is returned to the caller. -// Otherwise (no abort) nil is returned. - -var - Stop: PVirtualNode; - Abort: Boolean; - GetNextNode: TGetNextNodeProc; - WasIterating: Boolean; - -begin - Assert(Node <> FRoot, 'Node must not be the hidden root node.'); - - WasIterating := tsIterating in FStates; - DoStateChange([tsIterating]); - try - // prepare function to be used when advancing - if DoInit then - GetNextNode := GetNext - else - GetNextNode := GetNextNoInit; - - Abort := False; - if Node = nil then - Stop := nil - else - begin - if not (vsInitialized in Node.States) and DoInit then - InitNode(Node); - - // The stopper does not need to be initialized since it is not taken into the enumeration. - Stop := Node.NextSibling; - if Stop = nil then - begin - Stop := Node; - repeat - Stop := Stop.Parent; - until (Stop = FRoot) or Assigned(Stop.NextSibling); - if Stop = FRoot then - Stop := nil - else - Stop := Stop.NextSibling; - end; - end; - - // Use first node if we start with the root. - if Node = nil then - Node := GetFirstNoInit; - - if Assigned(Node) then - begin - if not (vsInitialized in Node.States) and DoInit then - InitNode(Node); - - // Skip given node if only the child nodes are requested. - if ChildNodesOnly then - begin - if Node.ChildCount = 0 then - Node := nil - else - Node := GetNextNode(Node); - end; - - if Filter = [] then - begin - // unfiltered loop - while Assigned(Node) and (Node <> Stop) do - begin - Callback(Self, Node, Data, Abort); - if Abort then - Break; - Node := GetNextNode(Node); - end; - end - else - begin - // filtered loop - while Assigned(Node) and (Node <> Stop) do - begin - if Node.States * Filter = Filter then - Callback(Self, Node, Data, Abort); - if Abort then - Break; - Node := GetNextNode(Node); - end; - end; - end; - - if Abort then - Result := Node - else - Result := nil; - finally - if not WasIterating then - DoStateChange([], [tsIterating]); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.LoadFromFile(const FileName: TFileName); - -var - FileStream: TFileStream; - -begin - FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); - try - LoadFromStream(FileStream); - finally - FileStream.Free; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.LoadFromStream(Stream: TStream); - -// Clears the current content of the tree and loads a new structure from the given stream. - -var - ThisID: TMagicID; - Version, - Count: Cardinal; - Node: PVirtualNode; - -begin - if not (toReadOnly in FOptions.MiscOptions) then - begin - Clear; - // Check first whether this is a stream we can read. - if Stream.Read(ThisID, SizeOf(TMagicID)) < SizeOf(TMagicID) then - ShowError(SStreamTooSmall, hcTFStreamTooSmall); - - if (ThisID[0] = MagicID[0]) and - (ThisID[1] = MagicID[1]) and - (ThisID[2] = MagicID[2]) and - (ThisID[5] = MagicID[5]) then - begin - Version := Word(ThisID[3]); - if Version <= VTTreeStreamVersion then - begin - BeginUpdate; - try - if Version < 2 then - Count := MaxInt - else - Stream.ReadBuffer(Count, SizeOf(Count)); - - while (Stream.Position < Stream.Size) and (Count > 0) do - begin - Dec(Count); - Node := MakeNewNode; - InternalConnectNode(Node, FRoot, Self, amAddChildLast); - InternalAddFromStream(Stream, Version, Node); - end; - DoNodeCopied(nil); - if Assigned(FOnLoadTree) then - FOnLoadTree(Self, Stream); - finally - EndUpdate; - end; - end - else - ShowError(SWrongStreamVersion, hcTFWrongStreamVersion); - end - else - ShowError(SWrongStreamFormat, hcTFWrongStreamFormat); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.MeasureItemHeight(const Canvas: TCanvas; Node: PVirtualNode); - -// If the height of the given node has not yet been measured then do it now. - -var - NewNodeHeight: Integer; - -begin - if not (vsHeightMeasured in Node.States) then - begin - Include(Node.States, vsHeightMeasured); - if (toVariableNodeHeight in FOptions.MiscOptions) then - begin - NewNodeHeight := Node.NodeHeight; - // Anonymous methods help to make this thread safe easily. - if (MainThreadId <> GetCurrentThreadId) then - TThread.Synchronize(nil, - procedure - begin - DoMeasureItem(Canvas, Node, NewNodeHeight); - SetNodeHeight(Node, NewNodeHeight); - end - ) - else - begin - DoMeasureItem(Canvas, Node, NewNodeHeight); - SetNodeHeight(Node, NewNodeHeight); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.MoveTo(Node: PVirtualNode; Tree: TBaseVirtualTree; Mode: TVTNodeAttachMode; - ChildrenOnly: Boolean); - -// A simplified method to allow to move nodes to the root of another tree. - -begin - MoveTo(Node, Tree.FRoot, Mode, ChildrenOnly); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.MoveTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode; ChildrenOnly: Boolean); - -// Moves the given node (and all its children) to Target. Source must belong to the tree instance which calls this -// MoveTo method. Mode determines how to connect Source to Target. -// This method might involve a change of the tree if Target belongs to a different tree than Source. - -var - TargetTree: TBaseVirtualTree; - Allowed: Boolean; - NewNode: PVirtualNode; - Stream: TMemoryStream; - -begin - Assert(TreeFromNode(Source) = Self, 'The source tree must contain the source node.'); - - // When moving nodes then source and target must not be the same node unless only the source's children are - // moved and they are inserted before or after the node itself. - Allowed := (Source <> Target) or ((Mode in [amInsertBefore, amInsertAfter]) and ChildrenOnly); - - if Allowed and (Mode <> amNoWhere) and Assigned(Source) and (Source <> FRoot) and - not (toReadOnly in FOptions.MiscOptions) then - begin - // Assume that an empty destination means the root in this (the source) tree. - if Target = nil then - begin - TargetTree := Self; - Target := FRoot; - Mode := amAddChildFirst; - end - else - TargetTree := TreeFromNode(Target); - - if Target = TargetTree.FRoot then - begin - case Mode of - amInsertBefore: - Mode := amAddChildFirst; - amInsertAfter: - Mode := amAddChildLast; - end; - end; - - // Make sure the target node is initialized. - if not (vsInitialized in Target.States) then - TargetTree.InitNode(Target) - else - if (vsHasChildren in Target.States) and (Target.ChildCount = 0) then - TargetTree.InitChildren(Target); - - if TargetTree = Self then - begin - // Simple case: move node(s) within the same tree. - if Target = FRoot then - Allowed := DoNodeMoving(Source, nil) - else - Allowed := DoNodeMoving(Source, Target); - if Allowed then - begin - // Check first that Source is not added as new child to a target node which - // is already a child of Source. - // Consider the case Source and Target are the same node, but only child nodes are moved. - if (Source <> Target) and HasAsParent(Target, Source) then - ShowError(SWrongMoveError, hcTFWrongMoveError); - - if not ChildrenOnly then - begin - // Disconnect from old location. - InternalDisconnectNode(Source, True); - // Connect to new location. - InternalConnectNode(Source, Target, Self, Mode); - DoNodeMoved(Source); - end - else - begin - // Only child nodes should be moved. Insertion order depends on move mode. - if Mode = amAddChildFirst then - begin - Source := Source.LastChild; - while Assigned(Source) do - begin - NewNode := Source.PrevSibling; - // Disconnect from old location. - InternalDisconnectNode(Source, True, False); - // Connect to new location. - InternalConnectNode(Source, Target, Self, Mode); - DoNodeMoved(Source); - Source := NewNode; - end; - end - else - begin - Source := Source.FirstChild; - while Assigned(Source) do - begin - NewNode := Source.NextSibling; - // Disconnect from old location. - InternalDisconnectNode(Source, True, False); - // Connect to new location. - InternalConnectNode(Source, Target, Self, Mode); - DoNodeMoved(Source); - Source := NewNode; - end; - end; - end; - end; - end - else - begin - // Difficult case: move node(s) to another tree. - // In opposition to node copying we ask only once if moving is allowed because - // we cannot take back a move once done. - if Target = TargetTree.FRoot then - Allowed := DoNodeMoving(Source, nil) - else - Allowed := DoNodeMoving(Source, Target); - - if Allowed then - begin - Stream := TMemoryStream.Create; - try - // Write all nodes into a temporary stream depending on the ChildrenOnly flag. - if not ChildrenOnly then - WriteNode(Stream, Source) - else - begin - Source := Source.FirstChild; - while Assigned(Source) do - begin - WriteNode(Stream, Source); - Source := Source.NextSibling; - end; - end; - // Now load the serialized nodes into the target node (tree). - TargetTree.BeginUpdate; - try - Stream.Position := 0; - while Stream.Position < Stream.Size do - begin - NewNode := TargetTree.MakeNewNode; - InternalConnectNode(NewNode, Target, TargetTree, Mode); - TargetTree.InternalAddFromStream(Stream, VTTreeStreamVersion, NewNode); - DoNodeMoved(NewNode); - end; - finally - TargetTree.EndUpdate; - end; - finally - Stream.Free; - end; - // finally delete original nodes - BeginUpdate; - try - if ChildrenOnly then - DeleteChildren(Source) - else - DeleteNode(Source); - finally - EndUpdate; - end; - end; - end; - - InvalidateCache; - if (FUpdateCount = 0) and Allowed then - begin - ValidateCache; - UpdateScrollBars(True); - Invalidate; - if TargetTree <> Self then - TargetTree.Invalidate; - end; - StructureChange(Source, crNodeMoved); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.PaintTree(TargetCanvas: TCanvas; Window: TRect; Target: TPoint; - PaintOptions: TVTInternalPaintOptions; PixelFormat: TPixelFormat); - -// This is the core paint routine of the tree. It is responsible for maintaining the paint cycles per node as well -// as coordinating drawing of the various parts of the tree image. -// TargetCanvas is the canvas to which to draw the tree image. This is usually the tree window itself but could well -// be a bitmap or printer canvas. -// Window determines which part of the entire tree image to draw. The full size of the virtual image is determined -// by GetTreeRect. -// Target is the position in TargetCanvas where to draw the tree part specified by Window. -// PaintOptions determines what of the tree to draw. For different tasks usually different parts need to be drawn, with -// a full image in the window, selected only nodes for a drag image etc. - -const - ImageKind: array[Boolean] of TVTImageKind = (ikNormal, ikSelected); - -var - DrawSelectionRect, - UseBackground, - ShowCheckImages, - UseColumns, - IsMainColumn: Boolean; - - IndentSize, - ButtonY: Integer; // Y position of toggle button within the node's rect - LineImage: TLineImage; - PaintInfo: TVTPaintInfo; // all necessary information about a node to pass to the paint routines - - R, // the area of an entire node in its local coordinate - TargetRect, // the area of a node (part) in the target canvas - SelectionRect, // ordered rectangle used for drawing the selection focus rect - ClipRect: TRect; // area to which the canvas will be clipped when painting a node's content - NextColumn: TColumnIndex; - BaseOffset: Integer; // top position of the top node to draw given in absolute tree coordinates - NodeBitmap: TBitmap; // small buffer to draw flicker free - MaximumRight, // maximum horizontal target position - MaximumBottom: Integer; // maximum vertical target position - SelectLevel: Integer; // > 0 if current node is selected or child/grandchild etc. of a selected node - FirstColumn: TColumnIndex; // index of first column which is at least partially visible in the given window - - MaxRight, - ColLeft, - ColRight: Integer; - - SavedTargetDC: Integer; - PaintWidth: Integer; - CurrentNodeHeight: Integer; - lUseSelectedBkColor: Boolean; // determines if the dotted grid lines need to be painted in selection color of background color - lEmptyListTextMargin: Integer; - - CellIsTouchingClientRight: Boolean; - CellIsInLastColumn: Boolean; - ColumnIsFixed: Boolean; - -begin - if not (tsPainting in FStates) then - begin - DoStateChange([tsPainting]); - try - DoBeforePaint(TargetCanvas); - - if poUnbuffered in PaintOptions then - SavedTargetDC := SaveDC(TargetCanvas.Handle) - else - SavedTargetDC := 0; - - // Prepare paint info structure. - ZeroMemory(@PaintInfo, SizeOf(PaintInfo)); - - PaintWidth := Window.Right - Window.Left; - - if not (poUnbuffered in PaintOptions) then - begin - // Create small bitmaps and initialize default values. - // The bitmaps are used to paint one node at a time and to draw the result to the target (e.g. screen) in one step, - // to prevent flickering. - NodeBitmap := TBitmap.Create; - // For alpha blending we need the 32 bit pixel format. For other targets there might be a need for a certain - // pixel format (e.g. printing). - if ((FDrawSelectionMode = smBlendedRectangle) or (tsUseThemes in FStates) or - (toUseBlendedSelection in FOptions.PaintOptions)) then - NodeBitmap.PixelFormat := pf32Bit - else - NodeBitmap.PixelFormat := PixelFormat; - - NodeBitmap.Width := PaintWidth; - - // Make sure the buffer bitmap and target bitmap use the same transformation mode. - SetMapMode(NodeBitmap.Canvas.Handle, GetMapMode(TargetCanvas.Handle)); - PaintInfo.Canvas := NodeBitmap.Canvas; - end - else - begin - PaintInfo.Canvas := TargetCanvas; - NodeBitmap := nil; - end; - - // Lock the canvas to avoid that it gets freed on the way. - PaintInfo.Canvas.Lock; - try - // Prepare the current selection rectangle once. The corner points are absolute tree coordinates. - SelectionRect := OrderRect(FNewSelRect); - DrawSelectionRect := IsMouseSelecting and not IsRectEmpty(SelectionRect) and (GetKeyState(VK_LBUTTON) < 0); - - // R represents an entire node (all columns), but is a bit unprecise when it comes to - // trees without any column defined, because FRangeX only represents the maximum width of all - // nodes in the client area (not all defined nodes). There might be, however, wider nodes somewhere. Without full - // validation I cannot better determine the width, though. By using at least the control's width it is ensured - // that the tree is fully displayed on screen. - R := Rect(0, 0, Max(FRangeX, ClientWidth), 0); - - // For quick checks some intermediate variables are used. - UseBackground := (toShowBackground in FOptions.PaintOptions) and Assigned(FBackground.Graphic) and - (poBackground in PaintOptions); - ShowCheckImages := Assigned(FCheckImages) and (toCheckSupport in FOptions.MiscOptions); - UseColumns := FHeader.UseColumns; - - // Adjust paint options to tree settings. Hide selection if told so or the tree is unfocused. - if (toAlwaysHideSelection in FOptions.PaintOptions) or - (not Focused and (toHideSelection in FOptions.PaintOptions)) then - Exclude(PaintOptions, poDrawSelection); - if toHideFocusRect in FOptions.PaintOptions then - Exclude(PaintOptions, poDrawFocusRect); - - // Determine node to start drawing with. - BaseOffset := 0; - PaintInfo.Node := GetNodeAt(0, Window.Top, False, BaseOffset); - if PaintInfo.Node = nil then - BaseOffset := Window.Top; - - // Transform selection rectangle into node bitmap coordinates. - if DrawSelectionRect then - OffsetRect(SelectionRect, 0, -BaseOffset); - - // The target rectangle holds the coordinates of the exact area to blit in target canvas coordinates. - // It is usually smaller than an entire node and wanders while the paint loop advances. - MaximumRight := Target.X + (Window.Right - Window.Left); - MaximumBottom := Target.Y + (Window.Bottom - Window.Top); - - TargetRect := Rect(Target.X, Target.Y - (Window.Top - BaseOffset), MaximumRight, 0); - TargetRect.Bottom := TargetRect.Top; - TargetCanvas.Font := Self.Font; - - // This marker gets the index of the first column which is visible in the given window. - // This is needed for column based background colors. - FirstColumn := InvalidColumn; - - if Assigned(PaintInfo.Node) then - begin - - // ----- main node paint loop - while Assigned(PaintInfo.Node) do - begin - // Determine LineImage, SelectionLevel and IndentSize - SelectLevel := DetermineLineImageAndSelectLevel(PaintInfo.Node, LineImage); - IndentSize := Length(LineImage); - - // Initialize node if not already done. - if not (vsInitialized in PaintInfo.Node.States) then - InitNode(PaintInfo.Node); - if (vsSelected in PaintInfo.Node.States) and not (toChildrenAbove in FOptions.PaintOptions) then - Inc(SelectLevel); - - // Ensure the node's height is determined. - MeasureItemHeight(PaintInfo.Canvas, PaintInfo.Node); - - // Adjust the brush origin for dotted lines depending on the current source position. - // It is applied some lines later, as the canvas might get reallocated, when changing the node bitmap. - PaintInfo.BrushOrigin := Point(Window.Left and 1, BaseOffset and 1); - Inc(BaseOffset, PaintInfo.Node.NodeHeight); - - TargetRect.Bottom := TargetRect.Top + PaintInfo.Node.NodeHeight; - - // If poSelectedOnly is active then do the following stuff only for selected nodes or nodes - // which are children of selected nodes. - if (SelectLevel > 0) or not (poSelectedOnly in PaintOptions) then - begin - if not (poUnbuffered in PaintOptions) then - begin - // Adjust height of temporary node bitmap. - with NodeBitmap do - begin - if Height <> PaintInfo.Node.NodeHeight then - begin - // Avoid that the VCL copies the bitmap while changing its height. - Height := 0; - Height := PaintInfo.Node.NodeHeight; - SetCanvasOrigin(Canvas, Window.Left, 0); - end; - end; - end - else - begin - SetCanvasOrigin(PaintInfo.Canvas, -TargetRect.Left + Window.Left, -TargetRect.Top); - ClipCanvas(PaintInfo.Canvas, Rect(0, 0, TargetRect.Right - TargetRect.Left, - Min(TargetRect.Bottom - TargetRect.Top, MaximumBottom - TargetRect.Top))); // See issue #579 - end; - - // Set the origin of the canvas' brush. This depends on the node heights. - with PaintInfo do - SetBrushOrigin(Canvas, BrushOrigin.X, BrushOrigin.Y); - - CurrentNodeHeight := PaintInfo.Node.NodeHeight; - R.Bottom := CurrentNodeHeight; - - // Let application decide whether the node should normally be drawn or by the application itself. - if not DoBeforeItemPaint(PaintInfo.Canvas, PaintInfo.Node, R) then - begin - // Init paint options for the background painting. - PaintInfo.PaintOptions := PaintOptions; - - // The node background can contain a single color, a bitmap or can be drawn by the application. - ClearNodeBackground(PaintInfo, UseBackground, True, Rect(Window.Left, TargetRect.Top, Window.Right, - TargetRect.Bottom)); - - // Prepare column, position and node clipping rectangle. - PaintInfo.CellRect := R; - if UseColumns then - InitializeFirstColumnValues(PaintInfo); - - // Now go through all visible columns (there's still one run if columns aren't used). - with FHeader.Columns do - begin - while ((PaintInfo.Column > InvalidColumn) or not UseColumns) - and (PaintInfo.CellRect.Left < Window.Right) do - begin - if UseColumns then - begin - PaintInfo.Column := FPositionToIndex[PaintInfo.Position]; - if FirstColumn = InvalidColumn then - FirstColumn := PaintInfo.Column; - PaintInfo.BidiMode := Items[PaintInfo.Column].BiDiMode; - PaintInfo.Alignment := Items[PaintInfo.Column].Alignment; - end - else - begin - PaintInfo.Column := NoColumn; - PaintInfo.BidiMode := BidiMode; - PaintInfo.Alignment := FAlignment; - end; - GetOffSets(PaintInfo.Node, PaintInfo.Offsets, TVTElement.ofsText, PaintInfo.Column); - - PaintInfo.PaintOptions := PaintOptions; - with PaintInfo do - begin - if (tsEditing in FStates) and (Node = FFocusedNode) and - ((Column = FEditColumn) or not UseColumns) then - Exclude(PaintOptions, poDrawSelection); - if not UseColumns or - ((vsSelected in Node.States) and (toFullRowSelect in FOptions.SelectionOptions) and - (poDrawSelection in PaintOptions)) or - (coParentColor in Items[PaintInfo.Column].Options) or - ((coStyleColor in Items[PaintInfo.Column].Options) and VclStyleEnabled) - then - Exclude(PaintOptions, poColumnColor); - end; - IsMainColumn := PaintInfo.Column = FHeader.MainColumn; - - // Consider bidi mode here. In RTL context means left alignment actually right alignment and vice versa. - if PaintInfo.BidiMode <> bdLeftToRight then - ChangeBiDiModeAlignment(PaintInfo.Alignment); - - // Paint the current cell if it is marked as being visible or columns aren't used and - // if this cell belongs to the main column if only the main column should be drawn. - if (not UseColumns or (coVisible in Items[PaintInfo.Column].Options)) and - (not (poMainOnly in PaintOptions) or IsMainColumn) then - begin - AdjustPaintCellRect(PaintInfo, NextColumn); - - // Paint the cell only if it is in the current window. - if PaintInfo.CellRect.Right > Window.Left then - begin - with PaintInfo do - begin - // Fill in remaining values in the paint info structure. - NodeWidth := DoGetNodeWidth(Node, Column, Canvas); - - if ShowCheckImages and IsMainColumn then - begin - ImageInfo[iiCheck].Index := GetCheckImage(Node); - ImageInfo[iiCheck].Images := FCheckImages; - ImageInfo[iiCheck].Ghosted := False; - end - else - ImageInfo[iiCheck].Index := -1; - GetImageIndex(PaintInfo, ikState, iiState); - GetImageIndex(PaintInfo, ImageKind[vsSelected in Node.States], iiNormal); - - CalculateVerticalAlignments(PaintInfo, ButtonY); - // Take the space for the tree lines into account. - PaintInfo.AdjustImageCoordinates(); - if UseColumns then - begin - ClipRect := CellRect; - if poUnbuffered in PaintOptions then - begin - ClipRect.Left := Max(ClipRect.Left, Window.Left); - ClipRect.Right := Min(ClipRect.Right, Window.Right); - ClipRect.Top := Max(ClipRect.Top, Window.Top - (BaseOffset - CurrentNodeHeight)); - ClipRect.Bottom := ClipRect.Bottom - Max(TargetRect.Bottom - MaximumBottom, 0); - end; - ClipCanvas(Canvas, ClipRect); - end; - - // Paint the horizontal grid line. - if (poGridLines in PaintOptions) and (toShowHorzGridLines in FOptions.PaintOptions) then - begin - Canvas.Font.Color := FColors.GridLineColor; - if IsMainColumn and (FLineMode = lmBands) then - begin - if BidiMode = bdLeftToRight then - begin - DrawDottedHLine(PaintInfo, CellRect.Left + PaintInfo.Offsets[ofsCheckBox] - fImagesMargin, CellRect.Right - 1, CellRect.Bottom - 1); - end - else - begin - DrawDottedHLine(PaintInfo, CellRect.Left, CellRect.Right - IfThen(toFixedIndent in FOptions.PaintOptions, 1, IndentSize) * Integer(FIndent) - 1, CellRect.Bottom - 1); - end; - end - else - DrawDottedHLine(PaintInfo, CellRect.Left, CellRect.Right, CellRect.Bottom - 1); - - Dec(CellRect.Bottom); - Dec(ContentRect.Bottom); - end; - - if UseColumns then - begin - // Paint vertical grid line. - if (poGridLines in PaintOptions) and (toShowVertGridLines in FOptions.PaintOptions) then - begin - // These variables and the nested if conditions shall make the logic - // easier to understand. - CellIsTouchingClientRight := PaintInfo.CellRect.Right = ClientRect.Right; - CellIsInLastColumn := Position = TColumnPosition(Count - 1); - ColumnIsFixed := coFixed in FHeader.Columns[Column].Options; - - // Don't draw if this is the last column and the header is in autosize mode. - if not ((hoAutoResize in FHeader.Options) and CellIsInLastColumn) then - begin - // We have to take spanned cells into account which we determine - // by checking if CellRect.Right equals the Window.Right. - // But since the PaintTree procedure is called twice in - // TBaseVirtualTree.Paint (i.e. for fixed columns and other columns. - // CellIsTouchingClientRight does not work for fixed columns.) - // we have to paint fixed column grid line anyway. - if not CellIsTouchingClientRight or ColumnIsFixed then - begin - if (BidiMode = bdLeftToRight) or not ColumnIsEmpty(Node, Column) then - begin - Canvas.Font.Color := FColors.GridLineColor; - lUseSelectedBkColor := (poDrawSelection in PaintOptions) and (toFullRowSelect in FOptions.SelectionOptions) and - (vsSelected in Node.States) and not (toUseBlendedSelection in FOptions.PaintOptions) and not - (tsUseExplorerTheme in FStates); - DrawDottedVLine(PaintInfo, CellRect.Top, CellRect.Bottom, CellRect.Right - 1, lUseSelectedBkColor); - end; - - Dec(CellRect.Right); - Dec(ContentRect.Right); - end; - end; - end; - end; - - // Prepare background and focus rect for the current cell. - PrepareCell(PaintInfo, Window.Left, PaintWidth); - - // Some parts are only drawn for the main column. - if IsMainColumn then - begin - if (toShowTreeLines in FOptions.PaintOptions) and - (not (toHideTreeLinesIfThemed in FOptions.PaintOptions) or - not (tsUseThemes in FStates)) then - PaintTreeLines(PaintInfo, IfThen(toFixedIndent in FOptions.PaintOptions, 1, - IndentSize), LineImage); - // Show node button if allowed, if there child nodes and at least one of the child - // nodes is visible or auto button hiding is disabled. - if (toShowButtons in FOptions.PaintOptions) and (vsHasChildren in Node.States) and - not ((vsAllChildrenHidden in Node.States) and - (toAutoHideButtons in TreeOptions.AutoOptions)) and - ((toShowRoot in TreeOptions.PaintOptions) or (GetNodeLevel(Node) > 0)) - then - PaintNodeButton(Canvas, Node, Column, CellRect, Offsets[ofsToggleButton], ButtonY, BidiMode); // Relative X position of toggle button is needed for proper BiDi calculation - - if ImageInfo[iiCheck].Index > -1 then - PaintCheckImage(Canvas, PaintInfo.ImageInfo[iiCheck], vsSelected in PaintInfo.Node.States); - end; - - if ImageInfo[iiState].Index > -1 then - PaintImage(PaintInfo, iiState, False); - if ImageInfo[iiNormal].Index > -1 then - PaintImage(PaintInfo, iiNormal, True); - - // Now let descendants or applications draw whatever they want, - // but don't draw the node if it is currently being edited. - if not ((tsEditing in FStates) and (Node = FFocusedNode) and - ((Column = FEditColumn) or not UseColumns)) then - DoPaintNode(PaintInfo); - - DoAfterCellPaint(Canvas, Node, Column, CellRect); - end; - end; - - // leave after first run if columns aren't used - if not UseColumns then - Break; - end - else - NextColumn := GetNextVisibleColumn(PaintInfo.Column); - - SelectClipRgn(PaintInfo.Canvas.Handle, 0); - // Stop column loop if there are no further columns in the given window. - if (PaintInfo.CellRect.Left >= Window.Right) or (NextColumn = InvalidColumn) then - Break; - - // Move on to next column which might not be the one immediately following the current one - // because of auto span feature. - PaintInfo.Position := Items[NextColumn].Position; - - // Move clip rectangle and continue. - if coVisible in Items[NextColumn].Options then - with PaintInfo do - begin - Items[NextColumn].GetAbsoluteBounds(CellRect.Left, CellRect.Right); - CellRect.Bottom := Node.NodeHeight; - ContentRect.Bottom := Node.NodeHeight; - end; - end; - end; - - // This node is finished, notify descendants/application. - with PaintInfo do - begin - DoAfterItemPaint(Canvas, Node, R); - - // Final touch for this node: mark it if it is the current drop target node. - if (Node = FDropTargetNode) and (toShowDropmark in FOptions.PaintOptions) and - (poDrawDropMark in PaintOptions) then - DoPaintDropMark(Canvas, Node, R); - end; - end; // if not DoBeforeItemPaint() (no custom drawing) - - - with PaintInfo.Canvas do - begin - if DrawSelectionRect then - begin - PaintSelectionRectangle(PaintInfo.Canvas, Window.Left, SelectionRect, Rect(0, 0, PaintWidth, - CurrentNodeHeight)); - end; - - // Put the constructed node image onto the target canvas. - if not (poUnbuffered in PaintOptions) then - with TWithSafeRect(TargetRect), NodeBitmap do - BitBlt(TargetCanvas.Handle, Left, Top, Width, Height, Canvas.Handle, Window.Left, 0, SRCCOPY); - end; - end; - - Inc(TargetRect.Top, PaintInfo.Node.NodeHeight); - if TargetRect.Top >= MaximumBottom then - Break; - - // Keep selection rectangle coordinates in sync. - if DrawSelectionRect then - OffsetRect(SelectionRect, 0, -PaintInfo.Node.NodeHeight); - - // Advance to next visible node. - PaintInfo.Node := GetNextVisible(PaintInfo.Node, True); - end; - end; - - // Erase rest of window not covered by a node. - if TargetRect.Top < MaximumBottom then - begin - // Keep the horizontal target position to determine the selection rectangle offset later (if necessary). - BaseOffset := Target.X; - Target := TargetRect.TopLeft; - R := Rect(TargetRect.Left, 0, TargetRect.Left, MaximumBottom - Target.Y); - TargetRect := Rect(0, 0, MaximumRight - Target.X, MaximumBottom - Target.Y); - - if not (poUnbuffered in PaintOptions) then - begin - // Avoid unnecessary copying of bitmap content. This will destroy the DC handle too. - NodeBitmap.Height := 0; - NodeBitmap.PixelFormat := pf32Bit; - NodeBitmap.SetSize(TargetRect.Right - TargetRect.Left, TargetRect.Bottom - TargetRect.Top); - end; - - // Call back application/descendants whether they want to erase this area. - if not DoPaintBackground(PaintInfo.Canvas, TargetRect) then - begin - if UseBackground then - begin - SetCanvasOrigin(PaintInfo.Canvas, 0, 0); - if toStaticBackground in TreeOptions.PaintOptions then - StaticBackground(FBackground, PaintInfo.Canvas, Target, TargetRect, FColors.BackGroundColor) - else - TileBackground(FBackground, PaintInfo.Canvas, Target, TargetRect, FColors.BackGroundColor); - end - else - begin - // Consider here also colors of the columns. - SetCanvasOrigin(PaintInfo.Canvas, Target.X, 0); // This line caused issue #313 when it was placed above the if-statement - if UseColumns then - begin - with FHeader.Columns do - begin - // If there is no content in the tree then the first column has not yet been determined. - if FirstColumn = InvalidColumn then - begin - FirstColumn := GetFirstVisibleColumn; - repeat - if FirstColumn <> InvalidColumn then - begin - R.Left := Items[FirstColumn].Left; - R.Right := R.Left + Items[FirstColumn].Width; - if R.Right > TargetRect.Left then - Break; - FirstColumn := GetNextVisibleColumn(FirstColumn); - end; - until FirstColumn = InvalidColumn; - end - else - begin - R.Left := Items[FirstColumn].Left; - R.Right := R.Left + Items[FirstColumn].Width; - end; - - // Initialize MaxRight. - MaxRight := Target.X - 1; - - PaintInfo.Canvas.Font.Color := FColors.GridLineColor; - while (FirstColumn <> InvalidColumn) and (MaxRight < TargetRect.Right + Target.X) do - begin - // Determine left and right coordinate of the current column - ColLeft := Items[FirstColumn].Left; - ColRight := (ColLeft + Items[FirstColumn].Width); - - // Check wether this column needs to be painted at all. - if (ColRight >= MaxRight) then - begin - R.Left := MaxRight; // Continue where we left off - R.Right := ColRight; // Paint to the right of the column - MaxRight := ColRight; // And record were to start the next column. - - if (poGridLines in PaintOptions) and - (toFullVertGridLines in FOptions.PaintOptions) and - (toShowVertGridLines in FOptions.PaintOptions) and - (not (hoAutoResize in FHeader.Options) or (Cardinal(FirstColumn) < TColumnPosition(Count - 1))) then - begin - DrawDottedVLine(PaintInfo, R.Top, R.Bottom, R.Right - 1); - Dec(R.Right); - end; - - if not (coParentColor in Items[FirstColumn].Options) then - PaintInfo.Canvas.Brush.Color := Items[FirstColumn].Color - else - PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor; - PaintInfo.Canvas.FillRect(R); - end; - FirstColumn := GetNextVisibleColumn(FirstColumn); - end; - - // Erase also the part of the tree not covert by a column. - if R.Right < TargetRect.Right + Target.X then - begin - R.Left := R.Right; - R.Right := TargetRect.Right + Target.X; - // Prevent erasing the last vertical grid line. - if (poGridLines in PaintOptions) and - (toFullVertGridLines in FOptions.PaintOptions) and (toShowVertGridLines in FOptions.PaintOptions) and - (not (hoAutoResize in FHeader.Options)) then - Inc(R.Left); - PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor; - PaintInfo.Canvas.FillRect(R); - end; - end; - SetCanvasOrigin(PaintInfo.Canvas, 0, 0); - end - else - begin - // No columns nor bitmap background. Simply erase it with the tree color. - SetCanvasOrigin(PaintInfo.Canvas, 0, 0); - PaintInfo.Canvas.Brush.Color := FColors.BackGroundColor; - PaintInfo.Canvas.FillRect(TargetRect); - end; - end; - end; - SetCanvasOrigin(PaintInfo.Canvas, 0, 0); - - if DrawSelectionRect then - begin - R := OrderRect(FNewSelRect); - // Remap the selection rectangle to the current window of the tree. - // Since Target has been used for other tasks BaseOffset got the left extent of the target position here. - OffsetRect(R, -Target.X + BaseOffset - Window.Left, -Target.Y + FOffsetY); - SetBrushOrigin(PaintInfo.Canvas, 0, Target.X and 1); - PaintSelectionRectangle(PaintInfo.Canvas, 0, R, TargetRect); - end; - - if not (poUnBuffered in PaintOptions) then - with Target, NodeBitmap do - BitBlt(TargetCanvas.Handle, X, Y, Width, Height, Canvas.Handle, 0, 0, SRCCOPY); - end; - finally - PaintInfo.Canvas.Unlock; - if poUnbuffered in PaintOptions then - RestoreDC(TargetCanvas.Handle, SavedTargetDC) - else - NodeBitmap.Free; - end;//try..finally - - if (FEmptyListMessage <> '') and ((ChildCount[nil] = 0) or (GetFirstVisible = nil)) then - begin - // output a message if no items are to display - Canvas.Font := Self.Font; - Canvas.Font.Size := Round(Canvas.Font.Size * 1.25); - SetBkMode(TargetCanvas.Handle, TRANSPARENT); - lEmptyListTextMargin := ScaledPixels(Max(cDefaultTextMargin, Self.TextMargin) * 2); - R.Left := OffSetX + lEmptyListTextMargin; - R.Top := lEmptyListTextMargin; - R.Right := R.Left + Width - lEmptyListTextMargin; - R.Bottom := Height - lEmptyListTextMargin; - TargetCanvas.Font.Color := StyleServices.GetStyleFontColor(TStyleFont.sfTreeItemTextDisabled);//clGrayText; - TargetCanvas.TextRect(R, FEmptyListMessage, [tfNoClip, tfLeft, tfWordBreak, tfExpandTabs]); - end; - - DoAfterPaint(TargetCanvas); - finally - DoStateChange([], [tsPainting]); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.PasteFromClipboard: Boolean; - -// Reads what is currently on the clipboard into the tree (if the format is supported). -// Note: If the application wants to have text or special formats to be inserted then it must implement -// its own code (OLE). Here only the native tree format is accepted. - -var - Data: IDataObject; - Source: TBaseVirtualTree; - -begin - Result := False; - if not (toReadOnly in FOptions.MiscOptions) then - begin - if OleGetClipboard(Data) <> S_OK then - ShowError(SClipboardFailed, hcTFClipboardFailed) - else - begin - // Try to get the source tree of the operation to optimize the operation. - Source := GetTreeFromDataObject(Data); - Result := ProcessOLEData(Source, Data, FFocusedNode, FDefaultPasteMode, Assigned(Source) and - (tsCutPending in Source.FStates)); - if Assigned(Source) then - begin - if Source <> Self then - Source.FinishCutOrCopy - else - DoStateChange([], [tsCutPending]); - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.PrepareDragImage(HotSpot: TPoint; const DataObject: IDataObject); - -// Initiates an image drag operation. HotSpot is the position of the mouse in client coordinates. - -var - PaintOptions: TVTInternalPaintOptions; - TreeRect, - PaintRect: TRect; - LocalSpot, - ImagePos, - PaintTarget: TPoint; - Image: TBitmap; - -begin - if CanShowDragImage then - begin - // Determine the drag rectangle which is a square around the hot spot. Operate in virtual tree space. - LocalSpot := HotSpot; - Dec(LocalSpot.X, -FEffectiveOffsetX); - Dec(LocalSpot.Y, FOffsetY); - TreeRect := Rect(LocalSpot.X - FDragWidth div 2, LocalSpot.Y - FDragHeight div 2, LocalSpot.X + FDragWidth div 2, - LocalSpot.Y + FDragHeight div 2); - - // Check that we have a valid rectangle. - PaintRect := TreeRect; - with TWithSafeRect(TreeRect) do - begin - if Left < 0 then - begin - PaintTarget.X := -Left; - PaintRect.Left := 0; - end - else - PaintTarget.X := 0; - if Top < 0 then - begin - PaintTarget.Y := -Top; - PaintRect.Top := 0; - end - else - PaintTarget.Y := 0; - end; - - Image := TBitmap.Create; - with Image do - try - PixelFormat := pf32Bit; - SetSize(TreeRect.Right - TreeRect.Left, TreeRect.Bottom - TreeRect.Top); - // Erase the entire image with the color key value, for the case not everything - // in the image is covered by the tree image. - Canvas.Brush.Color := FColors.BackGroundColor; - Canvas.FillRect(Rect(0, 0, Width, Height)); - - PaintOptions := [poDrawSelection, poSelectedOnly]; - if FDragImageKind = diMainColumnOnly then - Include(PaintOptions, poMainOnly); - PaintTree(Image.Canvas, PaintRect, PaintTarget, PaintOptions); - - // Once we have got the drag image we can convert all necessary coordinates into screen space. - OffsetRect(TreeRect, -FEffectiveOffsetX, FOffsetY); - ImagePos := ClientToScreen(TreeRect.TopLeft); - HotSpot := ClientToScreen(HotSpot); - - FDragImage.ColorKey := FColors.BackGroundColor; - FDragImage.PrepareDrag(Image, ImagePos, HotSpot, DataObject); - finally - Image.Free; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.Print(Printer: TPrinter; PrintHeader: Boolean); - -var - SaveTreeFont: TFont; // Remembers the tree's current font. - SaveHeaderFont: TFont; // Remembers the header's current font. - ImgRect, // Describes the dimensions of Image. - TreeRect, // The total VTree dimensions. - DestRect, // Dimensions of PrinterImage. - SrcRect: TRect; // Clip dimensions from Image -> PrinterImage - P: TPoint; // Used by PaintTree. - Options: TVTInternalPaintOptions; // Used by PaintTree. - Image, // Complete Tree is drawn to this image. - PrinterImage: TBitmap; // This is the image that gets printed. - SaveColor: TColor; // Remembers the VTree Color. - pTxtHeight, // Height of font in the TPrinter.Canvas - vTxtHeight, // Height of font in the VTree Canvas - vPageWidth, - vPageHeight, // Printer height in VTree resolution - xPageNum, yPageNum, // # of pages (except the occasional last one) - xPage, yPage: Integer; // Loop counter - Scale: Extended; // Scale factor between Printer Canvas and VTree Canvas - LogFont: TLogFont; - -begin - if Assigned(Printer) then - begin - BeginUpdate; - - // Grid lines are the only parts which are desirable when printing. - Options := [poGridLines]; - - // Remember the tree font. - SaveTreeFont := TFont.Create; - SaveTreeFont.Assign(Font); - // Create a new font for printing which does not use clear type output (but is antialiased, if possible) - // and which has the highest possible quality. - GetObject(Font.Handle, SizeOf(TLogFont), @LogFont); - LogFont.lfQuality := ANTIALIASED_QUALITY; - Font.Handle := CreateFontIndirect(LogFont); - - // Create an image that will hold the complete VTree - Image := TBitmap.Create; - Image.PixelFormat := pf32Bit; - PrinterImage := nil; - try - TreeRect := GetTreeRect; - - Image.Width := TreeRect.Right - TreeRect.Left; - P := Point(0, 0); - if (hoVisible in FHeader.Options) and PrintHeader then - begin - Inc(TreeRect.Bottom, FHeader.Height); - Inc(P.Y, FHeader.Height); - end; - Image.Height := TreeRect.Bottom - TreeRect.Top; - - ImgRect.Left := 0; - ImgRect.Top := 0; - ImgRect.Right := Image.Width; - - // Force the background to white color during the rendering. - SaveColor := FColors.BackGroundColor; - Color := clWhite; - // Print header if it is visible. - if (hoVisible in FHeader.Options) and PrintHeader then - begin - SaveHeaderFont := TFont.Create; - try - SaveHeaderFont.Assign(FHeader.Font); - // Create a new font for printing which does not use clear type output (but is antialiased, if possible) - // and which has the highest possible quality. - GetObject(FHeader.Font.Handle, SizeOf(TLogFont), @LogFont); - LogFont.lfQuality := ANTIALIASED_QUALITY; - FHeader.Font.Handle := CreateFontIndirect(LogFont); - ImgRect.Bottom := FHeader.Height; - FHeader.Columns.PaintHeader(Image.Canvas.Handle, ImgRect, 0); - FHeader.Font := SaveHeaderFont; - finally - SaveHeaderFont.Free; - end; - end; - // The image's height is already adjusted for the header if it is visible. - ImgRect.Bottom := Image.Height; - - PaintTree(Image.Canvas, ImgRect, P, Options, pf32Bit); - Color := SaveColor; - - // Activate the printer - Printer.BeginDoc; - Printer.Canvas.Font := Font; - - // Now we can calculate the scaling : - pTxtHeight := Printer.Canvas.TextHeight('Tj'); - vTxtHeight := Canvas.TextHeight('Tj'); - - Scale := pTxtHeight / vTxtHeight; - - // Create an Image that has the same dimensions as the printer canvas but - // scaled to the VTree resolution: - PrinterImage := TBitmap.Create; - - vPageHeight := Round(Printer.PageHeight / Scale); - vPageWidth := Round(Printer.PageWidth / Scale); - - // We do a minumum of one page. - xPageNum := Trunc(Image.Width / vPageWidth); - yPageNum := Trunc(Image.Height / vPageHeight); - - PrinterImage.SetSize(vPageWidth, vPageHeight); - - // Split vertically: - for yPage := 0 to yPageNum do - begin - DestRect.Left := 0; - DestRect.Top := 0; - DestRect.Right := PrinterImage.Width; - DestRect.Bottom := PrinterImage.Height; - - // Split horizontally: - for xPage := 0 to xPageNum do - begin - SrcRect.Left := vPageWidth * xPage; - SrcRect.Top := vPageHeight * yPage; - SrcRect.Right := vPageWidth * xPage + PrinterImage.Width; - SrcRect.Bottom := SrcRect.Top + vPageHeight; - - // Clear the image - PrinterImage.Canvas.Brush.Color := clWhite; - PrinterImage.Canvas.FillRect(Rect(0, 0, PrinterImage.Width, PrinterImage.Height)); - PrinterImage.Canvas.CopyRect(DestRect, Image.Canvas, SrcRect); - PrtStretchDrawDIB(Printer.Canvas, Rect(0, 0, Printer.PageWidth, Printer.PageHeight - 1), PrinterImage); - if xPage <> xPageNum then - Printer.NewPage; - end; - if yPage <> yPageNum then - Printer.NewPage; - end; - - // Restore tree font. - Font := SaveTreeFont; - SaveTreeFont.Free; - Printer.EndDoc; - finally - PrinterImage.Free; - Image.Free; - EndUpdate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ProcessDrop(const DataObject: IDataObject; TargetNode: PVirtualNode; var Effect: Integer; - Mode: TVTNodeAttachMode): Boolean; - -// Recreates the (sub) tree structure serialized into memory and provided by DataObject. The new nodes are attached to -// the passed node or FRoot if TargetNode is nil. -// Returns True on success, i.e. the CF_VIRTUALTREE format is supported by the data object and the structure could be -// recreated, otherwise False. - -var - Source: TBaseVirtualTree; - -begin - Result := False; - if Mode = amNoWhere then - Effect := DROPEFFECT_NONE - else - begin - BeginUpdate; - // try to get the source tree of the operation - Source := GetTreeFromDataObject(DataObject); - if Assigned(Source) then - Source.BeginUpdate; - try - try - // Before adding the new nodes try to optimize the operation if source and target tree reside in - // the same application and operation is a move. - if ((Effect and DROPEFFECT_MOVE) <> 0) and Assigned(Source) then - begin - // If both copy and move are specified then prefer a copy because this is not destructing. - Result := ProcessOLEData(Source, DataObject, TargetNode, Mode, (Effect and DROPEFFECT_COPY) = 0); - // Since we made an optimized move or a copy there's no reason to act further after DoDragging returns. - Effect := DROPEFFECT_NONE; - end - else - // Act only if move or copy operation is requested. - if (Effect and (DROPEFFECT_MOVE or DROPEFFECT_COPY)) <> 0 then - Result := ProcessOLEData(Source, DataObject, TargetNode, Mode, False) - else - Result := False; - except - Effect := DROPEFFECT_NONE; - end; - finally - if Assigned(Source) then - Source.EndUpdate; - EndUpdate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -type - // needed to handle OLE global memory objects - TOLEMemoryStream = class(TCustomMemoryStream) - public - function Write(const Buffer; Count: Integer): Integer; override; - end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TOLEMemoryStream.Write(const Buffer; Count: Integer): Integer; - -begin - raise EStreamError.CreateRes(PResStringRec(@SCantWriteResourceStreamError)); -end; - -//----------------- TBaseVirtualTree ----------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoDrawHint(Canvas: TCanvas; Node: PVirtualNode; R: - TRect; Column: TColumnIndex); - -begin - if Assigned(FOnDrawHint) then - FOnDrawHint(Self, Canvas, Node, R, Column); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoGetHintSize(Node: PVirtualNode; Column: - TColumnIndex; var R: TRect); - -begin - if Assigned(FOnGetHintSize) then - FOnGetHintSize(Self, Node, Column, R); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.DoGetHintKind(Node: PVirtualNode; Column: - TColumnIndex; var Kind: TVTHintKind); - -begin - if Assigned(FOnGetHintKind) then - FOnGetHintKind(Self, Node, Column, Kind) - else - Kind := DefaultHintKind; -end; - -function TBaseVirtualTree.GetDefaultHintKind: TVTHintKind; - -begin - Result := vhkText; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ProcessOLEData(Source: TBaseVirtualTree; const DataObject: IDataObject; TargetNode: PVirtualNode; - Mode: TVTNodeAttachMode; Optimized: Boolean): Boolean; - -// Recreates the (sub) tree structure serialized into memory and provided by DataObject. The new nodes are attached to -// the passed node or FRoot if TargetNode is nil according to Mode. Optimized can be set to True if the entire operation -// happens within the same process (i.e. sender and receiver of the OLE operation are located in the same process). -// Optimize = True makes only sense if the operation to carry out is a move hence it is also the indication of the -// operation to be done here. Source is the source of the OLE data and only of use (and usually assigned) when -// an OLE operation takes place in the same application. -// Returns True on success, i.e. the CF_VIRTUALTREE format is supported by the data object and the structure could be -// recreated, otherwise False. - -var - Medium: TStgMedium; - Stream: TStream; - Data: Pointer; - Node: PVirtualNode; - Nodes: TNodeArray; - I: Integer; - Res: HRESULT; - ChangeReason: TChangeReason; - -begin - Nodes := nil; - // Check the data format available by the data object. - with StandardOLEFormat do - begin - // Read best format. - cfFormat := CF_VIRTUALTREE; - end; - Result := DataObject.QueryGetData(StandardOLEFormat) = S_OK; - if Result and not (toReadOnly in FOptions.MiscOptions) then - begin - BeginUpdate; - Result := False; - try - if TargetNode = nil then - TargetNode := FRoot; - if TargetNode = FRoot then - begin - case Mode of - amInsertBefore: - Mode := amAddChildFirst; - amInsertAfter: - Mode := amAddChildLast; - end; - end; - - // Optimized means source is known and in the same process so we can access its pointers, which avoids duplicating - // the data while doing a serialization. Can only be used with cut'n paste and drag'n drop with move effect. - if Optimized then - begin - if tsOLEDragging in Source.FStates then - Nodes := Source.FDragSelection - else - Nodes := Source.GetSortedCutCopySet(True); - - if Mode in [amInsertBefore,amAddChildLast] then - begin - for I := 0 to High(Nodes) do - if not HasAsParent(TargetNode, Nodes[I]) then - Source.MoveTo(Nodes[I], TargetNode, Mode, False); - end - else - begin - for I := High(Nodes) downto 0 do - if not HasAsParent(TargetNode, Nodes[I]) then - Source.MoveTo(Nodes[I], TargetNode, Mode, False); - end; - Result := True; - end - else - begin - if Source = Self then - ChangeReason := crNodeCopied - else - ChangeReason := crNodeAdded; - Res := DataObject.GetData(StandardOLEFormat, Medium); - if Res = S_OK then - begin - case Medium.tymed of - TYMED_ISTREAM, // IStream interface - TYMED_HGLOBAL: // global memory block - begin - Stream := nil; - if Medium.tymed = TYMED_ISTREAM then - Stream := TOLEStream.Create(IUnknown(Medium.stm) as IStream) - else - begin - Data := GlobalLock(Medium.hGlobal); - if Assigned(Data) then - begin - // Get the total size of data to retrieve. - I := PCardinal(Data)^; - Inc(PCardinal(Data)); - Stream := TOLEMemoryStream.Create; - TOLEMemoryStream(Stream).SetPointer(Data, I); - end; - end; - - if Assigned(Stream) then - try - while Stream.Position < Stream.Size do - begin - Node := MakeNewNode; - InternalConnectNode(Node, TargetNode, Self, Mode); - InternalAddFromStream(Stream, VTTreeStreamVersion, Node); - // This seems a bit strange because of the callback for granting to add the node - // which actually comes after the node has been added. The reason is that the node must - // contain valid data otherwise I don't see how the application can make a funded decision. - if not DoNodeCopying(Node, TargetNode) then - begin - DeleteNode(Node); - end - else - begin - DoNodeCopied(Node); - StructureChange(Node, ChangeReason); - // In order to maintain the same node order when restoring nodes in the case of amInsertAfter - // we have to move the reference node continously. Othwise we would end up with reversed node order. - if Mode = amInsertAfter then - TargetNode := Node; - end; - end; - Result := True; - finally - Stream.Free; - if Medium.tymed = TYMED_HGLOBAL then - GlobalUnlock(Medium.hGlobal); - end; - end; - end; - ReleaseStgMedium(Medium); - end; - end; - finally - EndUpdate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ReinitChildren(Node: PVirtualNode; Recursive: Boolean); - -// Forces all child nodes of Node to be reinitialized. -// If Recursive is True then also the grandchildren are reinitialized. - -var - Run: PVirtualNode; - -begin - if Assigned(Node) then - begin - InitChildren(Node); - Run := Node.FirstChild; - end - else - begin - InitChildren(FRoot); - Run := FRoot.FirstChild; - end; - - while Assigned(Run) do - begin - ReinitNode(Run, Recursive); - Run := Run.NextSibling; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ReinitNode(Node: PVirtualNode; Recursive: Boolean); - -// Forces the given node and all its children (if recursive is True) to be initialized again without -// modifying any data in the nodes nor deleting children (unless the application requests a different amount). - -begin - if Assigned(Node) and (Node <> FRoot) then - begin - // Remove dynamic styles. - Node.States := Node.States - [vsChecking, vsCutOrCopy, vsDeleting, vsHeightMeasured]; - if vsInitialized in Node.States then - InitNode(Node); - end; - - if Recursive and (Node.ChildCount > 0) then // Prevent previoulsy uninitilaized children from being initialized. Issue #1145 - ReinitChildren(Node, True); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.RepaintNode(Node: PVirtualNode); - -// Causes an immediate repaint of the given node. - -var - R: Trect; - -begin - if Assigned(Node) and (Node <> FRoot) then - begin - R := GetDisplayRect(Node, NoColumn, False); - RedrawWindow(Handle, @R, 0, RDW_INVALIDATE or RDW_UPDATENOW or RDW_NOERASE or RDW_VALIDATE or RDW_NOCHILDREN); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ResetNode(Node: PVirtualNode); - -// Deletes all children of the given node and marks it as being uninitialized. - -begin - DoCancelEdit; - if (Node = nil) or (Node = FRoot) then - Clear - else - begin - DoReset(Node); - DeleteChildren(Node); - // Remove initialized and other dynamic styles, keep persistent styles. - Node.States := Node.States - [vsInitialized, vsChecking, vsCutOrCopy, vsDeleting, vsHasChildren, vsExpanded, - vsHeightMeasured]; - InvalidateNode(Node); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SaveToFile(const FileName: TFileName); - -// Saves the entire content of the tree into a file (see further notes in SaveToStream). - -var - FileStream: TFileStream; - -begin - FileStream := TFileStream.Create(FileName, fmCreate); - try - SaveToStream(FileStream); - finally - FileStream.Free; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SaveToStream(Stream: TStream; Node: PVirtualNode = nil); - -// Saves Node and all its children to Stream. If Node is nil then all top level nodes will be stored. -// Note: You should be careful about assuming what is actually saved. The problem here is that we are dealing with -// virtual data. The tree can so not know what it has to save. The only fact we reliably know is the tree's -// structure. To be flexible for future enhancements as well as unknown content (unknown to the tree class which -// is saving/loading the stream) a chunk based approach is used here. Every tree class handles only those -// chunks which are not handled by an anchestor class and are known by the class. -// -// The base tree class saves only the structure of the tree along with application provided data. descendants may -// optionally add their own chunks to store additional information. See: WriteChunks. - -var - Count: Cardinal; - -begin - Stream.Write(MagicID, SizeOf(MagicID)); - if Node = nil then - begin - // Keep number of top level nodes for easy restauration. - Count := FRoot.ChildCount; - Stream.WriteBuffer(Count, SizeOf(Count)); - - // Save entire tree here. - Node := FRoot.FirstChild; - while Assigned(Node) do - begin - WriteNode(Stream, Node); - Node := Node.NextSibling; - end; - end - else - begin - Count := 1; - Stream.WriteBuffer(Count, SizeOf(Count)); - WriteNode(Stream, Node); - end; - if Assigned(FOnSaveTree) then - FOnSaveTree(Self, Stream); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ScrollIntoView(Node: PVirtualNode; Center: Boolean; Horizontally: Boolean = False): Boolean; - -// Scrolls the tree so that the given node is in the client area and returns True if the tree really has been -// scrolled (e.g. to avoid further updates) else returns False. If extened focus is enabled then the tree will also -// be horizontally scrolled if needed. -// Note: All collapsed parents of the node are expanded. - -var - R: TRect; - Run: PVirtualNode; - UseColumns, - HScrollBarVisible: Boolean; - ScrolledVertically, - ScrolledHorizontally: Boolean; - -begin - ScrolledVertically := False; - ScrolledHorizontally := False; - - if Assigned(Node) and (Node <> FRoot) and HandleAllocated then // We don't want to create the handle if it has not yet been created, see issue #897 - begin - // Make sure all parents of the node are expanded. - Run := Node.Parent; - while Run <> FRoot do - begin - if not (vsExpanded in Run.States) then - ToggleNode(Run); - Run := Run.Parent; - end; - UseColumns := FHeader.UseColumns; - if UseColumns and FHeader.Columns.IsValidColumn(FFocusedColumn) then - R := GetDisplayRect(Node, FFocusedColumn, not (toGridExtensions in FOptions.MiscOptions)) - else - R := GetDisplayRect(Node, NoColumn, not (toGridExtensions in FOptions.MiscOptions)); - - // The returned rectangle can never be empty after the expand code above. - // 1) scroll vertically - if R.Top < 0 then - begin - if Center then - SetOffsetY(FOffsetY - R.Top + ClientHeight div 2) - else - SetOffsetY(FOffsetY - R.Top); - ScrolledVertically := True; - end - else - if (R.Bottom > ClientHeight) or Center then - begin - HScrollBarVisible := (ScrollBarOptions.ScrollBars in [System.UITypes.TScrollStyle.ssBoth, System.UITypes.TScrollStyle.ssHorizontal]) and - (ScrollBarOptions.AlwaysVisible or (Integer(FRangeX) > ClientWidth)); - if Center then - SetOffsetY(FOffsetY - R.Bottom + ClientHeight div 2) - else - SetOffsetY(FOffsetY - R.Bottom + ClientHeight); - // When scrolling up and the horizontal scroll appears because of the operation - // then we have to move up the node the horizontal scrollbar's height too - // in order to avoid that the scroll bar hides the node which we wanted to have in view. - if not UseColumns and not HScrollBarVisible and (Integer(FRangeX) > ClientWidth) then - SetOffsetY(FOffsetY - GetSystemMetrics(SM_CYHSCROLL)); - ScrolledVertically := True; - end; - - if Horizontally then - // 2) scroll horizontally - // Center only if there is enough space for the focused column, otherwise left align, see issue #397. - ScrolledHorizontally := ScrollIntoView(FFocusedColumn, Center and (R.Width <= (ClientWidth - Header.Columns.GetVisibleFixedWidth)), Node); - end; - - Result := ScrolledVertically or ScrolledHorizontally; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.ScaledPixels(pPixels: Integer): Integer; - - /// Returns the given pixels scaled to the current dpi assuming that we designed at 96dpi (100%) -begin - Result := MulDiv(pPixels, {$if CompilerVersion > 31}Self.FCurrentPPI{$else}Screen.PixelsPerInch{$ifend}, 96); -end; - -function TBaseVirtualTree.ScrollIntoView(Column: TColumnIndex; Center: Boolean; Node: PVirtualNode = nil): Boolean; - -// Scrolls the columns so that the given column is in the client area and returns True if the columns really have been -// scrolled (e.g. to avoid further updates) else returns False. - -var - ColumnLeft, - ColumnRight: Integer; - NewOffset: Integer; - R: TRect; - -begin - Result := False; - - if FHeader.UseColumns and FHeader.Columns.IsValidColumn(Column) then begin - ColumnLeft := Header.Columns.Items[Column].Left; - ColumnRight := ColumnLeft + Header.Columns.Items[Column].Width; - end else if Assigned(Node) and (toCenterScrollIntoView in FOptions.SelectionOptions) then begin - Center := False; - R := GetDisplayRect(Node, NoColumn, not (toGridExtensions in FOptions.MiscOptions)); - ColumnLeft := R.Left; - ColumnRight := R.Right; - end else - Exit; - - NewOffset := FEffectiveOffsetX; - if not (FHeader.UseColumns and (coFixed in Header.Columns[Column].Options)) and (not Center) then - begin - if ColumnRight > ClientWidth then - NewOffset := FEffectiveOffsetX + Min(ColumnRight - ClientWidth, - - (Header.Columns.GetVisibleFixedWidth - ColumnLeft)) - else if ColumnLeft < Header.Columns.GetVisibleFixedWidth then - NewOffset := FEffectiveOffsetX - (Header.Columns.GetVisibleFixedWidth - ColumnLeft); - if NewOffset <> FEffectiveOffsetX then - begin - if UseRightToLeftAlignment then - SetOffsetX(-Integer(FRangeX) + ClientWidth + NewOffset) - else - SetOffsetX(-NewOffset); - end; - Result := True; - end - else if Center then - begin - NewOffset := FEffectiveOffsetX + ColumnLeft - (Header.Columns.GetVisibleFixedWidth div 2) - (ClientWidth div 2) + ((ColumnRight - ColumnLeft) div 2); - if NewOffset <> FEffectiveOffsetX then - begin - if UseRightToLeftAlignment then - SetOffsetX(-Integer(FRangeX) + ClientWidth + NewOffset) - else - SetOffsetX(-NewOffset); - end; - Result := True; - end -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SelectAll(VisibleOnly: Boolean); - -// Select all nodes in the tree. -// If VisibleOnly is True then only visible nodes are selected. - -var - Run: PVirtualNode; - NextFunction: TGetNextNodeProc; -begin - if not FSelectionLocked and (toMultiSelect in FOptions.SelectionOptions) then - begin - ClearTempCache; - if VisibleOnly then - begin - Run := GetFirstVisible(nil, True); - NextFunction := GetNextVisible; - end - else - begin - Run := GetFirst; - NextFunction := GetNext; - end; - BeginUpdate(); // Improve performance, see issue #690 - try - while Assigned(Run) do - begin - if not(vsSelected in Run.States) then - InternalCacheNode(Run); - Run := NextFunction(Run); - end;//while - if FTempNodeCount > 0 then - AddToSelection(FTempNodeCache, FTempNodeCount); - ClearTempCache; - finally - EndUpdate(); - end;//try..finally - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.Sort(Node: PVirtualNode; Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); - -// Sorts the given node. The application is queried about how to sort via the OnCompareNodes event. -// Column is simply passed to the the compare function so the application can also sort in a particular column. -// In order to free the application from taking care about the sort direction the parameter Direction is used. -// This way the application can always sort in increasing order, while this method reorders nodes according to this flag. - - //--------------- local functions ------------------------------------------- - - function MergeAscending(A, B: PVirtualNode): PVirtualNode; - - // Merges A and B (which both must be sorted via Compare) into one list. - - var - Dummy: TVirtualNode; - CompareResult: Integer; - begin - // This avoids checking for Result = nil in the loops. - Result := @Dummy; - while Assigned(A) and Assigned(B) do - begin - if OperationCanceled then - CompareResult := 0 - else - CompareResult := DoCompare(A, B, Column); - - if CompareResult <= 0 then - begin - Result.NextSibling := A; - Result := A; - A := A.NextSibling; - end - else - begin - Result.NextSibling := B; - Result := B; - B := B.NextSibling; - end; - end; - - // Just append the list which is not nil (or set end of result list to nil if both lists are nil). - if Assigned(A) then - Result.NextSibling := A - else - Result.NextSibling := B; - // return start of the new merged list - Result := Dummy.NextSibling; - end; - - //--------------------------------------------------------------------------- - - function MergeDescending(A, B: PVirtualNode): PVirtualNode; - - // Merges A and B (which both must be sorted via Compare) into one list. - - var - Dummy: TVirtualNode; - CompareResult: Integer; - - begin - // this avoids checking for Result = nil in the loops - Result := @Dummy; - while Assigned(A) and Assigned(B) do - begin - if OperationCanceled then - CompareResult := 0 - else - CompareResult := DoCompare(A, B, Column); - - if CompareResult >= 0 then - begin - Result.NextSibling := A; - Result := A; - A := A.NextSibling; - end - else - begin - Result.NextSibling := B; - Result := B; - B := B.NextSibling; - end; - end; - - // Just append the list which is not nil (or set end of result list to nil if both lists are nil). - if Assigned(A) then - Result.NextSibling := A - else - Result.NextSibling := B; - // Return start of the newly merged list. - Result := Dummy.NextSibling; - end; - - //--------------------------------------------------------------------------- - - function MergeSortAscending(var Node: PVirtualNode; N: Cardinal): PVirtualNode; - - // Sorts the list of nodes given by Node (which must not be nil). - - var - A, B: PVirtualNode; - - begin - if N > 1 then - begin - A := MergeSortAscending(Node, N div 2); - B := MergeSortAscending(Node, (N + 1) div 2); - Result := MergeAscending(A, B); - end - else - begin - Result := Node; - Node := Node.NextSibling; - Result.NextSibling := nil; - end; - end; - - //--------------------------------------------------------------------------- - - function MergeSortDescending(var Node: PVirtualNode; N: Cardinal): PVirtualNode; - - // Sorts the list of nodes given by Node (which must not be nil). - - var - A, B: PVirtualNode; - - begin - if N > 1 then - begin - A := MergeSortDescending(Node, N div 2); - B := MergeSortDescending(Node, (N + 1) div 2); - Result := MergeDescending(A, B); - end - else - begin - Result := Node; - Node := Node.NextSibling; - Result.NextSibling := nil; - end; - end; - - //--------------- end local functions --------------------------------------- - -var - Run: PVirtualNode; - Index: Cardinal; - -begin - InterruptValidation; - if tsEditPending in FStates then - begin - StopTimer(EditTimer); - DoStateChange([], [tsEditPending]); - end; - - if not (tsEditing in FStates) or DoEndEdit then - begin - if Node = nil then - Node := FRoot; - if vsHasChildren in Node.States then - begin - if (Node.ChildCount = 0) and DoInit then - InitChildren(Node); - // Make sure the children are valid, so they can be sorted at all. - if DoInit and (Node.ChildCount > 0) then - ValidateChildren(Node, False); - // Child count might have changed. - if Node.ChildCount > 1 then - begin - StartOperation(okSortNode); - try - // Sort the linked list, check direction flag only once. - if Direction = sdAscending then - Node.FirstChild := MergeSortAscending(Node.FirstChild, Node.ChildCount) - else - Node.FirstChild := MergeSortDescending(Node.FirstChild, Node.ChildCount); - finally - EndOperation(okSortNode); - end; - // Consolidate the child list finally. - Run := Node.FirstChild; - Run.PrevSibling := nil; - Index := 0; - repeat - Run.Index := Index; - Inc(Index); - if Run.NextSibling = nil then - Break; - Run.NextSibling.PrevSibling := Run; - Run := Run.NextSibling; - until False; - Node.LastChild := Run; - - InvalidateCache; - end; - if FUpdateCount = 0 then - begin - ValidateCache; - Invalidate; - end; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.SortTree(Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); - - //--------------- local function -------------------------------------------- - - procedure DoSort(Node: PVirtualNode); - - // Recursively sorts Node and its child nodes. - - var - Run: PVirtualNode; - - begin - Sort(Node, Column, Direction, DoInit); - // Recurse to next level - Run := Node.FirstChild; - while Assigned(Run) and not FOperationCanceled do - begin - if DoInit and not (vsInitialized in Run.States) then - InitNode(Run); - if (vsInitialized in Run.States) and (not (toAutoSort in TreeOptions.AutoOptions) or Expanded[Run]) then // There is no need to sort collapsed branches - DoSort(Run); - Run := Run.NextSibling; - end; - end; - - //--------------- end local function ---------------------------------------- - -begin - if RootNode.TotalCount <= 2 then - Exit;//Nothing to do if there are one or zero nodes. RootNode.TotalCount is 1 if there are no nodes in the treee as the root node counts too here. - // Instead of wrapping the sort using BeginUpdate/EndUpdate simply the update counter - // is modified. Otherwise the EndUpdate call will recurse here. - Inc(FUpdateCount); - try - if Column > InvalidColumn then - begin - StartOperation(okSortTree); - try - DoSort(FRoot); - finally - EndOperation(okSortTree); - end; - end; - InvalidateCache; - finally - if FUpdateCount > 0 then - Dec(FUpdateCount); - if FUpdateCount = 0 then - begin - ValidateCache; - Invalidate; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ToggleNode(Node: PVirtualNode); - -// Changes a node's expand state to the opposite state. - -var - Child, - FirstVisible: PVirtualNode; - HeightDelta, - StepsR1, - StepsR2, - Steps: Integer; - TogglingTree, - ChildrenInView, - NeedFullInvalidate, - NeedUpdate, - NodeInView, - PosHoldable, - TotalFit: Boolean; - ToggleData: TToggleAnimationData; - - //--------------- local function -------------------------------------------- - - procedure PrepareAnimation; - - // Prepares ToggleData. - - var - R: TRect; - S: Integer; - M: TToggleAnimationMode; - - begin - with ToggleData do - begin - Window := Handle; - DC := GetDC(Handle); - - if (toShowBackground in FOptions.PaintOptions) and Assigned(FBackground.Graphic) then - Self.Brush.Style := bsClear - else - begin - Self.Brush.Style := bsSolid; - Self.Brush.Color := FColors.BackGroundColor; - end; - - Brush := Self.Brush.Handle; - - if (Mode1 <> tamNoScroll) and (Mode2 <> tamNoScroll) then - begin - if StepsR1 < StepsR2 then - begin - // As the primary rectangle is always R1 we will get a much smoother - // animation if R1 is the one that will be scrolled more. - R := R2; - R2 := R1; - R1 := R; - - M := Mode2; - Mode2 := Mode1; - Mode1 := M; - - S := StepsR2; - StepsR2 := StepsR1; - StepsR1 := S; - end; - ScaleFactor := StepsR2 / StepsR1; - MissedSteps := 0; - end; - - if Mode1 <> tamNoScroll then - Steps := StepsR1 - else - Steps := StepsR2; - end; - end; - - //--------------- end local function ---------------------------------------- - -begin - Assert(Assigned(Node), 'Node must not be nil.'); - - TogglingTree := tsToggling in FStates; - ChildrenInView := False; - HeightDelta := 0; - NeedFullInvalidate := False; - NeedUpdate := False; - NodeInView := False; - PosHoldable := False; - TotalFit := False; - - // We don't need to switch the expand state if the node is being deleted otherwise some - // updates (e.g. visible node count) are done twice with disasterous results). - if [vsDeleting, vsToggling] * Node.States = [] then - begin - try - DoStateChange([tsToggling]); - Include(Node.States, vsToggling); - - if vsExpanded in Node.States then - begin - if DoCollapsing(Node) then - begin - NeedUpdate := True; - - // Calculate the height delta right now as we need it for toChildrenAbove anyway. - HeightDelta := -Integer(Node.TotalHeight) + Integer(NodeHeight[Node]); - if (FUpdateCount = 0) and (toAnimatedToggle in FOptions.AnimationOptions) and not - (tsCollapsing in FStates) then - begin - if tsHint in Self.FStates then - Application.CancelHint; - UpdateWindow(Handle); - - // animated collapsing - with ToggleData do - begin - // Determine the animation behaviour and rectangle. If toChildrenAbove is set, the behaviour is depending - // on the position of the node to be collapsed. - R1 := GetDisplayRect(Node, NoColumn, False); - Mode2 := tamNoScroll; - if toChildrenAbove in FOptions.PaintOptions then - begin - PosHoldable := (FOffsetY + (Integer(Node.TotalHeight) - Integer(NodeHeight[Node]))) <= 0; - NodeInView := R1.Top < ClientHeight; - - StepsR1 := 0; - if NodeInView then - begin - if PosHoldable or not (toAdvancedAnimatedToggle in FOptions.AnimationOptions) then - begin - // Scroll the child nodes down. - Mode1 := tamScrollDown; - R1.Bottom := R1.Top; - R1.Top := 0; - StepsR1 := Min(R1.Bottom - R1.Top + 1, Integer(Node.TotalHeight) - Integer(NodeHeight[Node])); - end - else - begin - // The position cannot be kept. So scroll the node up to its future position. - Mode1 := tamScrollUp; - R1.Top := Max(0, R1.Top + HeightDelta); - R1.Bottom := ClientHeight; - StepsR1 := FOffsetY - HeightDelta; - end; - end; - end - else - begin - if (Integer(FRangeY) + FOffsetY - R1.Bottom + HeightDelta >= ClientHeight - R1.Bottom) or - (Integer(FRangeY) <= ClientHeight) or (FOffsetY = 0) or not - (toAdvancedAnimatedToggle in FOptions.AnimationOptions) then - begin - // Do a simple scroll up over the child nodes. - Mode1 := tamScrollUp; - Inc(R1.Top, NodeHeight[Node]); - R1.Bottom := ClientHeight; - StepsR1 := Min(R1.Bottom - R1.Top + 1, -HeightDelta); - end - else - begin - // Scroll the node down to its future position. As FOffsetY will change we need to invalidate the - // whole tree. - Mode1 := tamScrollDown; - StepsR1 := Min(-FOffsetY, ClientHeight - Integer(FRangeY) -FOffsetY - HeightDelta); - R1.Top := 0; - R1.Bottom := Min(ClientHeight, R1.Bottom + Steps); - NeedFullInvalidate := True; - end; - end; - - // No animation necessary if the node is below the current client height. - if R1.Top < ClientHeight then - begin - PrepareAnimation; - try - Animate(Steps, FAnimationDuration, ToggleCallback, @ToggleData); - finally - ReleaseDC(Window, DC); - end; - end; - end; - end; - - // collapse the node - AdjustTotalHeight(Node, IfThen(IsEffectivelyFiltered[Node], 0, NodeHeight[Node])); - if FullyVisible[Node] then - Dec(FVisibleCount, CountVisibleChildren(Node)); - Exclude(Node.States, vsExpanded); - DoCollapsed(Node); - - // Remove child nodes now, if enabled. - if (toAutoFreeOnCollapse in FOptions.AutoOptions) and (Node.ChildCount > 0) then - begin - DeleteChildren(Node); - Include(Node.States, vsHasChildren); - end; - end; - end - else - if DoExpanding(Node) then - begin - NeedUpdate := True; - // expand the node, need to adjust the height - if not (vsInitialized in Node.States) then - InitNode(Node); - if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then - InitChildren(Node); - - // Avoid setting the vsExpanded style if there are no child nodes. - if Node.ChildCount > 0 then - begin - // Iterate through the child nodes without initializing them. We have to determine the entire height. - Child := Node.FirstChild; - repeat - if vsVisible in Child.States then - begin - // Ensure the item height is measured - MeasureItemHeight(Canvas, Child); - - Inc(HeightDelta, Child.TotalHeight); - end; - Child := Child.NextSibling; - until Child = nil; - - // Getting the display rectangle is already done here as it is needed for toChildrenAbove in any case. - if (toChildrenAbove in FOptions.PaintOptions) or (FUpdateCount = 0) then - begin - with ToggleData do - begin - R1 := GetDisplayRect(Node, NoColumn, False); - Mode2 := tamNoScroll; - TotalFit := HeightDelta + Integer(NodeHeight[Node]) <= ClientHeight; - - if toChildrenAbove in FOptions.PaintOptions then - begin - // The main goal with toChildrenAbove being set is to keep the nodes visual position so the user does - // not get confused. Therefore we need to scroll the view when the expanding is done. - PosHoldable := TotalFit and (Integer(FRangeY) - ClientHeight >= 0) ; - ChildrenInView := (R1.Top - HeightDelta) >= 0; - NodeInView := R1.Bottom <= ClientHeight; - end - else - begin - PosHoldable := TotalFit; - ChildrenInView := R1.Bottom + HeightDelta <= ClientHeight; - end; - - R1.Bottom := ClientHeight; - end; - end; - - if FUpdateCount = 0 then - begin - // Do animated expanding if enabled. - if (ToggleData.R1.Top < ClientHeight) and ([tsPainting, tsExpanding] * FStates = []) and - (toAnimatedToggle in FOptions.AnimationOptions)then - begin - if tsHint in Self.FStates then - Application.CancelHint; - UpdateWindow(Handle); - // animated expanding - with ToggleData do - begin - if toChildrenAbove in FOptions.PaintOptions then - begin - // At first check if we hold the position, which is the most common case. - if not (toAdvancedAnimatedToggle in FOptions.AnimationOptions) or - (PosHoldable and ( (NodeInView and ChildrenInView) or not - (toAutoScrollOnExpand in FOptions.AutoOptions) )) then - begin - Mode1 := tamScrollUp; - R1 := Rect(R1.Left, 0, R1.Right, R1.Top); - StepsR1 := Min(HeightDelta, R1.Bottom); - end - else - begin - // If we will not hold the node's visual position we mostly scroll in both directions. - Mode1 := tamScrollDown; - Mode2 := tamScrollUp; - R2 := Rect(R1.Left, 0, R1.Right, R1.Top); - if not (toAutoScrollOnExpand in FOptions.AutoOptions) then - begin - // If we shall not or cannot scroll to the desired extent we calculate the new position (with - // max FOffsetY applied) and animate it that way. - StepsR1 := -FOffsetY - Max(Integer(FRangeY) + HeightDelta - ClientHeight, 0) + HeightDelta; - if (Integer(FRangeY) + HeightDelta - ClientHeight) <= 0 then - Mode2 := tamNoScroll - else - StepsR2 := Min(Integer(FRangeY) + HeightDelta - ClientHeight, R2.Bottom); - end - else - begin - if TotalFit and NodeInView and (Integer(FRangeY) + HeightDelta > ClientHeight) then - begin - // If the whole subtree will fit into the client area and the node is currently fully visible, - // the first child will be made the top node if possible. - if HeightDelta >= R1.Top then - StepsR1 := Abs(R1.Top - HeightDelta) - else - StepsR1 := ClientHeight - Integer(FRangeY); - end - else - if Integer(FRangeY) + HeightDelta <= ClientHeight then - begin - // We cannot make the first child the top node as we cannot scroll to that extent, - // so we do a simple scroll down. - Mode2 := tamNoScroll; - StepsR1 := HeightDelta; - end - else - // If the subtree does not fit into the client area at once, the expanded node will - // be made the bottom node. - StepsR1 := ClientHeight - R1.Top - Integer(NodeHeight[Node]); - - if Mode2 <> tamNoScroll then - begin - if StepsR1 > 0 then - StepsR2 := Min(R1.Top, HeightDelta - StepsR1) - else - begin - // If the node is already at the bottom scrolling is needed. - Mode1 := tamNoScroll; - StepsR2 := Min(HeightDelta, R1.Bottom); - end; - end; - end; - end; - end - else - begin - // toChildrenAbove is not set. - if (PosHoldable and ChildrenInView) or not (toAutoScrollOnExpand in FOptions.AutoOptions) or not - (toAdvancedAnimatedToggle in FOptions.AnimationOptions) or (R1.Top <= 0) then - begin - // If the node will stay at its visual position, do a simple down-scroll. - Mode1 := tamScrollDown; - Inc(R1.Top, NodeHeight[Node]); - StepsR1 := Min(R1.Bottom - R1.Top, HeightDelta); - end - else - begin - // We will not hold the nodes visual position so perform a double scroll. - Mode1 := tamScrollUp; - Mode2 := tamScrollDown; - - R1.Bottom := R1.Top + Integer(NodeHeight[Node]) + 1; - R1.Top := 0; - R2 := Rect(R1.Left, R1.Bottom, R1.Right, ClientHeight); - - StepsR1 := Min(HeightDelta - (ClientHeight - R2.Top), R1.Bottom - Integer(NodeHeight[Node])); - StepsR2 := ClientHeight - R2.Top; - end; - end; - - if ClientHeight >= R1.Top then - begin - PrepareAnimation; - try - Animate(Steps, FAnimationDuration, ToggleCallback, @ToggleData); - finally - ReleaseDC(Window, DC); - end; - end; - end; - end; - if toAutoSort in FOptions.AutoOptions then - Sort(Node, FHeader.SortColumn, FHeader.SortDirection, False); - end;// if UpdateCount = 0 - - Include(Node.States, vsExpanded); - AdjustTotalHeight(Node, HeightDelta, True); - if FullyVisible[Node] then - Inc(FVisibleCount, CountVisibleChildren(Node)); - - DoExpanded(Node); - end; - end; - - if NeedUpdate then - begin - InvalidateCache; - if FUpdateCount = 0 then - begin - ValidateCache; - if Node.ChildCount > 0 then - begin - UpdateRanges; - UpdateScrollBars(True); - if [tsPainting, tsExpanding] * FStates = [] then - begin - if (vsExpanded in Node.States) and ((toAutoScrollOnExpand in FOptions.AutoOptions) or - (toChildrenAbove in FOptions.PaintOptions)) then - begin - if toChildrenAbove in FOptions.PaintOptions then - begin - NeedFullInvalidate := True; - if (PosHoldable and ChildrenInView and NodeInView) or not - (toAutoScrollOnExpand in FOptions.AutoOptions) then - SetOffsetY(FOffsetY - Integer(HeightDelta)) - else - if TotalFit and NodeInView then - begin - FirstVisible := GetFirstVisible(Node, True); - if Assigned(FirstVisible) then // otherwise there is no visible child at all - SetOffsetY(FOffsetY - GetDisplayRect(FirstVisible, NoColumn, False).Top); - end - else - BottomNode := Node; - end - else - begin - // Scroll as much child nodes into view as possible if the node has been expanded. - if PosHoldable then - NeedFullInvalidate := ScrollIntoView(GetLastVisible(Node, True), False) - else - begin - TopNode := Node; - NeedFullInvalidate := True; - end; - end; - end - else - begin - // If we have collapsed the node or toAutoScrollOnExpand is not set, we try to keep the nodes - // visual position. - if toChildrenAbove in FOptions.PaintOptions then - SetOffsetY(FOffsetY - Integer(HeightDelta)); - NeedFullInvalidate := True; - end; - end; - - //UpdateScrollBars(True); Moved up - - // Check for automatically scrolled tree. - if NeedFullInvalidate then - Invalidate - else - InvalidateToBottom(Node); - end - else - InvalidateNode(Node); - end - else - begin - UpdateRanges; - UpdateScrollBars(True); - end; - end; - - finally - Exclude(Node.States, vsToggling); - if not TogglingTree then - DoStateChange([], [tsToggling]); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateHorizontalRange; - -begin - if FHeader.UseColumns then - SetRangeX(FHeader.Columns.TotalWidth) - else - SetRangeX(GetMaxRightExtend); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateHorizontalScrollBar(DoRepaint: Boolean); - -var - ScrollInfo: TScrollInfo; - -begin - UpdateHorizontalRange; - - if IsUpdating or not HandleAllocated then - Exit; - - // Adjust effect scroll offset depending on bidi mode. - if UseRightToLeftAlignment then - FEffectiveOffsetX := Integer(FRangeX) - ClientWidth + FOffsetX - else - FEffectiveOffsetX := -FOffsetX; - - if FScrollBarOptions.ScrollBars in [System.UITypes.TScrollStyle.ssHorizontal, System.UITypes.TScrollStyle.ssBoth] then - begin - ZeroMemory (@ScrollInfo, SizeOf(ScrollInfo)); - ScrollInfo.cbSize := SizeOf(ScrollInfo); - ScrollInfo.fMask := SIF_ALL; - GetScrollInfo(Handle, SB_HORZ, ScrollInfo); - - if (Integer(FRangeX) > ClientWidth) or FScrollBarOptions.AlwaysVisible then - begin - DoShowScrollBar(SB_HORZ, True); - - ScrollInfo.nMin := 0; - ScrollInfo.nMax := FRangeX; - ScrollInfo.nPos := FEffectiveOffsetX; - ScrollInfo.nPage := Max(0, ClientWidth + 1); - - ScrollInfo.fMask := SIF_ALL or ScrollMasks[FScrollBarOptions.AlwaysVisible]; - SetScrollInfo(Handle, SB_HORZ, ScrollInfo, DoRepaint); // 1 app freeze seen here in TreeSize 8.1.0 after ScaleForPpi() - if DoRepaint then - RedrawWindow(Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE); // Fixes issue #698 - end - else - begin - ScrollInfo.nMin := 0; - ScrollInfo.nMax := 0; - ScrollInfo.nPos := 0; - ScrollInfo.nPage := 0; - DoShowScrollBar(SB_HORZ, False); - SetScrollInfo(Handle, SB_HORZ, ScrollInfo, False); - end; - - // Since the position is automatically changed if it doesn't meet the range - // we better read the current position back to stay synchronized. - FEffectiveOffsetX := GetScrollPos(Handle, SB_HORZ); - if UseRightToLeftAlignment then - SetOffsetX(-Integer(FRangeX) + ClientWidth + FEffectiveOffsetX) - else - SetOffsetX(-FEffectiveOffsetX); - end - else - begin - DoShowScrollBar(SB_HORZ, False); - - // Reset the current horizontal offset to account for window resize etc. - SetOffsetX(FOffsetX); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateRanges; - -begin - UpdateVerticalRange; - UpdateHorizontalRange; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateScrollBars(DoRepaint: Boolean); - -// adjusts scrollbars to reflect current size and paint offset of the tree - -begin - if HandleAllocated then - begin - UpdateVerticalScrollBar(DoRepaint); - UpdateHorizontalScrollBar(DoRepaint); - Perform(CM_UPDATE_VCLSTYLE_SCROLLBARS,0,0); - end; -end; - -procedure TBaseVirtualTree.UpdateStyleElements; -begin - inherited; - UpdateHeaderRect; - FHeader.Columns.PaintHeader(Canvas, FHeaderRect, Point(0,0)); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateVerticalRange; - -begin - // Total node height includes the height of the invisible root node. - FRangeY := Cardinal(Int64(FRoot.TotalHeight) - FRoot.NodeHeight + FBottomSpace); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.UpdateVerticalScrollBar(DoRepaint: Boolean); - -var - ScrollInfo: TScrollInfo; - -begin - UpdateVerticalRange; - - if (IsUpdating) then - Exit; - Assert(GetCurrentThreadId = MainThreadId, 'UI controls like ' + Classname + ' and its scrollbars should only be manipulated through the main thread.'); - - if FScrollBarOptions.ScrollBars in [ssVertical, ssBoth] then - begin - ScrollInfo.cbSize := SizeOf(ScrollInfo); - ScrollInfo.fMask := SIF_ALL; - GetScrollInfo(Handle, SB_VERT, ScrollInfo); - - if (Integer(FRangeY) > ClientHeight) or FScrollBarOptions.AlwaysVisible then - begin - DoShowScrollBar(SB_VERT, True); - - ScrollInfo.nMin := 0; - ScrollInfo.nMax := FRangeY; - ScrollInfo.nPos := -FOffsetY; - ScrollInfo.nPage := Max(0, ClientHeight + 1); - - ScrollInfo.fMask := SIF_ALL or ScrollMasks[FScrollBarOptions.AlwaysVisible]; - SetScrollInfo(Handle, SB_VERT, ScrollInfo, DoRepaint); - end - else - begin - ScrollInfo.nMin := 0; - ScrollInfo.nMax := 0; - ScrollInfo.nPos := 0; - ScrollInfo.nPage := 0; - DoShowScrollBar(SB_VERT, False); - SetScrollInfo(Handle, SB_VERT, ScrollInfo, False); - end; - - // Since the position is automatically changed if it doesn't meet the range - // we better read the current position back to stay synchronized. - SetOffsetY(-GetScrollPos(Handle, SB_VERT)); - end - else - begin - DoShowScrollBar(SB_VERT, False); - - // Reset the current vertical offset to account for window resize etc. - SetOffsetY(FOffsetY); - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TBaseVirtualTree.UseRightToLeftReading: Boolean; - -// The tree can handle right-to-left reading also on non-middle-east systems, so we cannot use the same function as -// it is implemented in TControl. - -begin - Result := BiDiMode <> bdLeftToRight; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ValidateChildren(Node: PVirtualNode; Recursive: Boolean); - -// Ensures that the children of the given node (and all their children, if Recursive is True) are initialized. -// Node must already be initialized - -var - Child: PVirtualNode; - -begin - if Node = nil then - Node := FRoot; - - if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then - InitChildren(Node); - Child := Node.FirstChild; - while Assigned(Child) do - begin - ValidateNode(Child, Recursive); - Child := Child.NextSibling; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TBaseVirtualTree.ValidateNode(Node: PVirtualNode; Recursive: Boolean); - -// Ensures that the given node (and all its children, if Recursive is True) are initialized. - -var - Child: PVirtualNode; - -begin - if Node = nil then - Node := FRoot - else - if not (vsInitialized in Node.States) then - InitNode(Node); - - if Recursive then - begin - if (vsHasChildren in Node.States) and (Node.ChildCount = 0) then - InitChildren(Node); - Child := Node.FirstChild; - while Assigned(Child) do - begin - ValidateNode(Child, Recursive); - Child := Child.NextSibling; - end; - end; -end; - -//----------------- TCustomStringTreeOptions --------------------------------------------------------------------------- - -constructor TCustomStringTreeOptions.Create(AOwner: TBaseVirtualTree); - -begin - inherited; - - FStringOptions := DefaultStringOptions; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomStringTreeOptions.SetStringOptions(const Value: TVTStringOptions); - -var - ChangedOptions: TVTStringOptions; - -begin - if FStringOptions <> Value then - begin - // Exclusive ORing to get all entries wich are in either set but not in both. - ChangedOptions := FStringOptions + Value - (FStringOptions * Value); - FStringOptions := Value; - with FOwner do - if (toShowStaticText in ChangedOptions) and not (csLoading in ComponentState) and HandleAllocated then - Invalidate; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomStringTreeOptions.AssignTo(Dest: TPersistent); - -begin - if Dest is TCustomStringTreeOptions then - begin - with Dest as TCustomStringTreeOptions do - begin - StringOptions := Self.StringOptions; - EditOptions := Self.EditOptions; - end; - end; - - // Let ancestors assign their options to the destination class. - inherited; -end; - -//----------------- TVTEdit -------------------------------------------------------------------------------------------- - -// Implementation of a generic node caption editor. - -constructor TVTEdit.Create(Link: TStringEditLink); - -begin - inherited Create(nil); - if not Assigned(Link) then - raise EArgumentException.Create('Paramter Link must not be nil.'); - ShowHint := False; - ParentShowHint := False; - // This assignment increases the reference count for the interface. - FRefLink := Link; - // This reference is used to access the link. - FLink := Link; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.ClearLink; -begin - FLink := nil -end; - -//---------------------------------------------------------------------------------------------------------------------- -procedure TVTEdit.ClearRefLink; -begin - FRefLink := nil -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTEdit.CalcMinHeight: Integer; -var - textHeight : Integer; -begin - // Get the actual text height. - textHeight := GetTextSize.cy; - // The minimal height is the actual text height in pixels plus the the non client area. - Result := textHeight + (Height - ClientHeight); - // Also, proportionally to the text size, additional pixel(s) needs to be added for the caret. - Result := Result + Trunc(textHeight * 0.05); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.CMAutoAdjust(var Message: TMessage); - -begin - AutoAdjustSize; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.CMExit(var Message: TMessage); - -begin - if Assigned(FLink) and not FLink.Stopping then - with FLink, FTree do - begin - if (toAutoAcceptEditChange in TreeOptions.StringOptions) then - DoEndEdit - else - DoCancelEdit; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.CMRelease(var Message: TMessage); - -begin - Free; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.CNCommand(var Message: TWMCommand); - -begin - if Assigned(FLink) and Assigned(FLink.Tree) and (Message.NotifyCode = EN_UPDATE) and - not (vsMultiline in FLink.Node.States) then - // Instead directly calling AutoAdjustSize it is necessary on Win9x/Me to decouple this notification message - // and eventual resizing. Hence we use a message to accomplish that. - AutoAdjustSize() - else - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.WMChar(var Message: TWMChar); - -begin - if not (Message.CharCode in [VK_ESCAPE, VK_TAB]) then - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.WMDestroy(var Message: TWMDestroy); - -begin - // If editing stopped by other means than accept or cancel then we have to do default processing for - // pending changes. - if Assigned(FLink) and not FLink.Stopping and not (csRecreating in Self.ControlState) then - begin - with FLink, FTree do - begin - if (toAutoAcceptEditChange in TreeOptions.StringOptions) and Modified then - Text[FNode, FColumn] := FEdit.Text; - end; - FLink := nil; - FRefLink := nil; - end; - - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.WMGetDlgCode(var Message: TWMGetDlgCode); - -begin - inherited; - - Message.Result := Message.Result or DLGC_WANTALLKEYS or DLGC_WANTTAB or DLGC_WANTARROWS; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.WMKeyDown(var Message: TWMKeyDown); - -// Handles some control keys. - -var - Shift: TShiftState; - EndEdit: Boolean; - Tree: TBaseVirtualTree; - NextNode: PVirtualNode; - ColumnCandidate: Integer; - EditOptions: TVTEditOptions; - Column: TVirtualTreeColumn; -begin - Tree := FLink.Tree; - case Message.CharCode of - VK_ESCAPE: - begin - Tree.DoCancelEdit; - end; - VK_RETURN: - begin - EndEdit := not (vsMultiline in FLink.Node.States); - if not EndEdit then - begin - // If a multiline node is being edited the finish editing only if Ctrl+Enter was pressed, - // otherwise allow to insert line breaks into the text. - Shift := KeyDataToShiftState(Message.KeyData); - EndEdit := ssCtrl in Shift; - end; - if EndEdit then - begin - Tree := FLink.Tree; - FLink.Tree.InvalidateNode(FLink.Node); - NextNode := Tree.GetNextVisible(FLink.Node, True); - FLink.Tree.DoEndEdit; - - // get edit options for column as priority. If column has toDefaultEdit - // use global edit options for tree - EditOptions := Tree.TreeOptions.EditOptions; // default - ColumnCandidate := -1; - if Tree.Header.Columns.Count > 0 then // are there any columns? - begin - Column := Tree.Header.Columns[Tree.FocusedColumn]; - if Column.EditOptions <> toDefaultEdit then - EditOptions := Column.EditOptions; - - // next column candidate for toVerticalEdit and toHorizontalEdit - if Column.EditNextColumn <> -1 then - ColumnCandidate := Column.EditNextColumn; - end; - - case EditOptions of - toDefaultEdit: Tree.TrySetFocus; - toVerticalEdit: - if NextNode <> nil then - begin - Tree.FocusedNode := NextNode; - - // for toVerticalEdit ColumnCandidate is also proper, - // select ColumnCandidate column in row below - if ColumnCandidate <> -1 then - begin - Tree.FocusedColumn := ColumnCandidate; - Tree.EditColumn := ColumnCandidate; - end; - - if Tree.CanEdit(Tree.FocusedNode, Tree.FocusedColumn) then - Tree.DoEdit; - end; - toHorizontalEdit: - begin - if ColumnCandidate = -1 then - begin - // for toHorizontalEdit if property EditNextColumn is not used - // try to use just next column - ColumnCandidate := Tree.FocusedColumn+1; - while (ColumnCandidate < Tree.Header.Columns.Count) - and not Tree.CanEdit(Tree.FocusedNode, ColumnCandidate) - do - Inc(ColumnCandidate); - end - else - if not Tree.CanEdit(Tree.FocusedNode, ColumnCandidate) then - ColumnCandidate := Tree.Header.Columns.Count; // omit "focus/edit column" (see below) - - if ColumnCandidate < Tree.Header.Columns.Count then - begin - Tree.FocusedColumn := ColumnCandidate; - Tree.EditColumn := ColumnCandidate; - Tree.DoEdit; - end; - end; - end; - end; - end; - VK_UP: - begin - if not (vsMultiline in FLink.Node.States) then - Message.CharCode := VK_LEFT; - inherited; - end; - VK_DOWN: - begin - if not (vsMultiline in FLink.Node.States) then - Message.CharCode := VK_RIGHT; - inherited; - end; - VK_TAB: - begin - if Tree.IsEditing then - begin - Tree.InvalidateNode(FLink.Node); - if ssShift in KeyDataToShiftState(Message.KeyData) then - NextNode := Tree.GetPreviousVisible(FLink.Node, True) // Shift+Tab goes to previous mode - else - NextNode := Tree.GetNextVisible(FLink.Node, True); - Tree.EndEditNode; - // check NextNode, otherwise we got AV - if NextNode <> nil then - begin - // Continue editing next node - Tree.ClearSelection(); - Tree.Selected[NextNode] := True; - if Tree.CanEdit(Tree.FocusedNode, Tree.FocusedColumn) then - Tree.DoEdit; - end; - end; - end; - Ord('A'): - begin - if Tree.IsEditing and ([ssCtrl] = KeyboardStateToShiftState) then - begin - Self.SelectAll(); - Message.CharCode := 0; - end; - end; - else - inherited; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.AutoAdjustSize; - -// Changes the size of the edit to accomodate as much as possible of its text within its container window. -// NewChar describes the next character which will be added to the edit's text. - -var - Size: TSize; -begin - if not (vsMultiline in FLink.Node.States) and not (toGridExtensions in FLink.Tree.TreeOptions.MiscOptions{see issue #252}) then - begin - // avoid flicker - SendMessage(Handle, WM_SETREDRAW, 0, 0); - try - Size := GetTextSize; - Inc(Size.cx, 2 * FLink.Tree.FTextMargin); - // Repaint associated node if the edit becomes smaller. - if Size.cx < Width then - FLink.Tree.Invalidate(); - - if FLink.Alignment = taRightJustify then - FLink.SetBounds(Rect(Left + Width - Size.cx, Top, Left + Width, Top + Max(Size.cy, Height))) - else - FLink.SetBounds(Rect(Left, Top, Left + Size.cx, Top + Max(Size.cy, Height))); - finally - SendMessage(Handle, WM_SETREDRAW, 1, 0); - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.CreateParams(var Params: TCreateParams); - -begin - inherited; - if not Assigned(FLink.Node) then - exit; // Prevent AV exceptions occasionally seen in code below - - // Only with multiline style we can use the text formatting rectangle. - // This does not harm formatting as single line control, if we don't use word wrapping. - with Params do - begin - Style := Style or ES_MULTILINE; - if vsMultiline in FLink.Node.States then - Style := Style and not (ES_AUTOHSCROLL or WS_HSCROLL) or WS_VSCROLL or ES_AUTOVSCROLL; - if tsUseThemes in FLink.Tree.FStates then - begin - Style := Style and not WS_BORDER; - ExStyle := ExStyle or WS_EX_CLIENTEDGE; - end - else - begin - Style := Style or WS_BORDER; - ExStyle := ExStyle and not WS_EX_CLIENTEDGE; - end; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVTEdit.GetTextSize: TSize; -var - DC: HDC; - LastFont: THandle; -begin - DC := GetDC(Handle); - LastFont := SelectObject(DC, Font.Handle); - try - // Read needed space for the current text. - GetTextExtentPoint32(DC, PChar(Text+'yG'), Length(Text)+2, Result); - finally - SelectObject(DC, LastFont); - ReleaseDC(Handle, DC); - end; -end; - -procedure TVTEdit.KeyPress(var Key: Char); -begin - if (Key = #13) and Assigned(FLink) and not (vsMultiline in FLink.Node.States) then - Key := #0; // Filter out return keys as they will be added to the text, avoids #895 - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVTEdit.Release; - -begin - if HandleAllocated then - PostMessage(Handle, CM_RELEASE, 0, 0); -end; - -//----------------- TStringEditLink ------------------------------------------------------------------------------------ - -constructor TStringEditLink.Create; - -begin - inherited; - FEdit := TVTEdit.Create(Self); - with FEdit do - begin - Visible := False; - BorderStyle := bsSingle; - AutoSize := False; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -destructor TStringEditLink.Destroy; - -begin - if Assigned(FEdit) then - FEdit.Release; - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TStringEditLink.BeginEdit: Boolean; - -// Notifies the edit link that editing can start now. descendants may cancel node edit -// by returning False. - -begin - Result := not FStopping; - if Result then - begin - FEdit.Show; - FEdit.SelectAll; - FEdit.SetFocus; - FEdit.AutoAdjustSize; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TStringEditLink.SetEdit(const Value: TVTEdit); - -begin - if Assigned(FEdit) then - FEdit.Free; - FEdit := Value; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TStringEditLink.CancelEdit: Boolean; - -begin - Result := not FStopping; - if Result then - begin - FStopping := True; - FEdit.Hide; - FTree.CancelEditNode; - FEdit.ClearLink; - FEdit.ClearRefLink; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TStringEditLink.EndEdit: Boolean; - -begin - Result := not FStopping; - if Result then - try - FStopping := True; - if FEdit.Modified then - FTree.Text[FNode, FColumn] := FEdit.Text; - FEdit.Hide; - FEdit.ClearLink; - FEdit.ClearRefLink; - except - FStopping := False; - raise; - end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TStringEditLink.GetBounds: TRect; - -begin - Result := FEdit.BoundsRect; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TStringEditLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; - -// Retrieves the true text bounds from the owner tree. - -var - Text: string; - -begin - Result := Tree is TCustomVirtualStringTree; - if Result then - begin - if not Assigned(FEdit) then - begin - FEdit := TVTEdit.Create(Self); - FEdit.Visible := False; - FEdit.BorderStyle := bsSingle; - end; - FEdit.AutoSize := True; - FTree := Tree as TCustomVirtualStringTree; - FNode := Node; - FColumn := Column; - FEdit.Parent := Tree; - // Initial size, font and text of the node. - FTree.GetTextInfo(Node, Column, FEdit.Font, FTextBounds, Text); - FEdit.Font.Color := clWindowText; - FEdit.RecreateWnd; - FEdit.AutoSize := False; - FEdit.Text := Text; - - if Column <= NoColumn then - begin - FEdit.BidiMode := FTree.BidiMode; - FAlignment := FTree.Alignment; - end - else - begin - FEdit.BidiMode := FTree.Header.Columns[Column].BidiMode; - FAlignment := FTree.Header.Columns[Column].Alignment; - end; - - if FEdit.BidiMode <> bdLeftToRight then - ChangeBidiModeAlignment(FAlignment); + property Touch; + property OnColumnHeaderSpanning; end; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TStringEditLink.ProcessMessage(var Message: TMessage); - -begin - FEdit.WindowProc(Message); -end; //---------------------------------------------------------------------------------------------------------------------- -procedure TStringEditLink.SetBounds(R: TRect); +implementation +uses + System.TypInfo, // for migration stuff + System.StrUtils, + System.Types, // prevent inline compiler warning + System.UITypes, // prevent inline compiler warning + VirtualTrees.StyleHooks, + VirtualTrees.ClipBoard, + VirtualTrees.Utils, + VirtualTrees.Export, + VirtualTrees.EditLink, + VirtualTrees.BaseAncestorVcl{to eliminate H2443 about inline expanding} + ; -// Sets the outer bounds of the edit control and the actual edit area in the control. +const + cDefaultText = 'Node'; + RTLFlag: array[Boolean] of Integer = (0, ETO_RTLREADING); + AlignmentToDrawFlag: array[TAlignment] of Cardinal = (DT_LEFT, DT_RIGHT, DT_CENTER); + gInitialized: Integer = 0; // >0 if global structures have been initialized; otherwise 0 -var - lOffset, tOffset, height: Integer; - offsets : TVTOffsets; +//// initialization of stuff global to the unit +procedure InitializeGlobalStructures(); begin - if not FStopping then - begin - // Check if the provided rect height is smaller than the edit control height. - height := R.Bottom - R.Top; - if height < FEdit.ClientHeight then - begin - // If the height is smaller than the minimal height we must correct it, otherwise the caret will be invisible. - tOffset := FEdit.CalcMinHeight - height; - if tOffset > 0 then - Inc(R.Bottom, tOffset); - end; - - // Set the edit's bounds but make sure there's a minimum width and the right border does not - // extend beyond the parent's left/right border. - if R.Left < 0 then - R.Left := 0; - if R.Right - R.Left < 30 then - begin - if FAlignment = taRightJustify then - R.Left := R.Right - 30 - else - R.Right := R.Left + 30; - end; - if R.Right > FTree.ClientWidth then - R.Right := FTree.ClientWidth; - FEdit.BoundsRect := R; - - // The selected text shall exclude the text margins and be centered vertically. - // We have to take out the two pixel border of the edit control as well as a one pixel "edit border" the - // control leaves around the (selected) text. - R := FEdit.ClientRect; - - // If toGridExtensions are turned on, we can fine tune the left margin (or the right margin if RTL is on) - // of the text to exactly match the text in the tree cell. - if (toGridExtensions in FTree.TreeOptions.MiscOptions) and - ((FAlignment = taLeftJustify) and (FEdit.BidiMode = bdLeftToRight) or - (FAlignment = taRightJustify) and (FEdit.BidiMode <> bdLeftToRight)) then - begin - // Calculate needed text area offset. - FTree.GetOffsets(FNode, offsets, ofsText, FColumn); - if FColumn = FTree.Header.MainColumn then - begin - if offsets[ofsToggleButton] < 0 then - lOffset := -(offsets[ofsToggleButton] + 2) - else - lOffset := 0; - end - else - lOffset := offsets[ofsText] - offsets[ofsMargin] + 1; - // Apply the offset. - if FEdit.BidiMode = bdLeftToRight then - Inc(R.Left, lOffset) - else - Dec(R.Right, lOffset); - end; + if (gInitialized > 0) or (AtomicIncrement(gInitialized) <> 1) then // Ensure threadsafe that this code is executed only once + exit; - lOffset := IfThen(vsMultiline in FNode.States, 0, 2); - if tsUseThemes in FTree.TreeStates then - Inc(lOffset); - InflateRect(R, -FTree.TextMargin + lOffset, lOffset); - if not (vsMultiline in FNode.States) then - begin - tOffset := FTextBounds.Top - FEdit.Top; - // Do not apply a negative offset, the cursor will disappear. - if tOffset > 0 then - OffsetRect(R, 0, tOffset); - end; - R.Top := Max(-1, R.Top); // A value smaller than -1 will prevent the edit cursor from being shown by Windows, see issue #159 - R.Left := Max(-1, R.Left); - SendMessage(FEdit.Handle, EM_SETRECTNP, 0, LPARAM(@R)); - end; + // Clipboard format registration. + // Specialized string tree formats. + CF_HTML := RegisterVTClipboardFormat(CFSTR_HTML, TCustomVirtualStringTree, 80); + CF_VRTFNOOBJS := RegisterVTClipboardFormat(CFSTR_RTFNOOBJS, TCustomVirtualStringTree, 84); + CF_VRTF := RegisterVTClipboardFormat(CFSTR_RTF, TCustomVirtualStringTree, 85); + CF_CSV := RegisterVTClipboardFormat(CFSTR_CSV, TCustomVirtualStringTree, 90); + // Predefined clipboard formats. Just add them to the internal list. + RegisterVTClipboardFormat(CF_TEXT, TCustomVirtualStringTree, 100); + RegisterVTClipboardFormat(CF_UNICODETEXT, TCustomVirtualStringTree, 95); end; + //----------------- TCustomVirtualString ------------------------------------------------------------------------------- constructor TCustomVirtualStringTree.Create(AOwner: TComponent); begin + InitializeGlobalStructures(); inherited; FPreviouslySelected := nil; FDefaultText := cDefaultText; @@ -33735,7 +742,7 @@ function TCustomVirtualStringTree.GetImageText(Node: PVirtualNode; function TCustomVirtualStringTree.GetOptions: TCustomStringTreeOptions; begin - Result := FOptions as TCustomStringTreeOptions; + Result := inherited TreeOptions as TCustomStringTreeOptions; end; //---------------------------------------------------------------------------------------------------------------------- @@ -33778,35 +785,35 @@ procedure TCustomVirtualStringTree.InitializeTextProperties(var PaintInfo: TVTPa with PaintInfo do begin // Set default font values first. - Canvas.Font := Font; + Canvas.Font.Assign(Font); if Enabled then // Otherwise only those colors are used, which are passed from Font to Canvas.Font. - Canvas.Font.Color := FColors.NodeFontColor + Canvas.Font.Color := Colors.NodeFontColor else - Canvas.Font.Color := FColors.DisabledColor; + Canvas.Font.Color := Colors.DisabledColor; - if (toHotTrack in FOptions.PaintOptions) and (Node = FCurrentHotNode) then + if (toHotTrack in TreeOptions.PaintOptions) and (Node = HotNode) then begin - if not (tsUseExplorerTheme in FStates) then + if not (tsUseExplorerTheme in TreeStates) then begin - Canvas.Font.Style := Canvas.Font.Style + [fsUnderline]; - Canvas.Font.Color := FColors.HotColor; + Canvas.Font.Style := Canvas.Font.Style + [TFontStyle.fsUnderline]; + Canvas.Font.Color := Colors.HotColor; end; end; // Change the font color only if the node also is drawn in selected style. if poDrawSelection in PaintOptions then begin - if (Column = FFocusedColumn) or (toFullRowSelect in FOptions.SelectionOptions) then + if (Column = FocusedColumn) or (toFullRowSelect in TreeOptions.SelectionOptions) then begin - if Node = FDropTargetNode then + if Node = DropTargetNode then begin - if ((FLastDropMode = dmOnNode) or (vsSelected in Node.States)) then - Canvas.Font.Color := FColors.GetSelectedNodeFontColor(True); // See #1083, since drop highlight color is chosen independent of the focus state, we need to choose Font color also independent of it. + if ((LastDropMode = dmOnNode) or (vsSelected in Node.States)) then + Canvas.Font.Color := Colors.GetSelectedNodeFontColor(True); // See #1083, since drop highlight color is chosen independent of the focus state, we need to choose Font color also independent of it. end else if vsSelected in Node.States then begin - Canvas.Font.Color := FColors.GetSelectedNodeFontColor(Focused); + Canvas.Font.Color := Colors.GetSelectedNodeFontColor(Focused or (toPopupMode in TreeOptions.PaintOptions)); end; end; end; @@ -33824,21 +831,21 @@ procedure TCustomVirtualStringTree.PaintNormalText(var PaintInfo: TVTPaintInfo; // the node rectangle. The clipping rectangle comprises the entire node (including tree lines, buttons etc.). var - TripleWidth: Integer; + TripleWidth: TDimension; R: TRect; DrawFormat: Cardinal; - Height: Integer; - lNewNodeWidth: Integer; + Height: TDimension; + lNewNodeWidth: TDimension; begin InitializeTextProperties(PaintInfo); with PaintInfo do begin R := ContentRect; Canvas.TextFlags := 0; - InflateRect(R, -FTextMargin, 0); + InflateRect(R, -TextMargin, 0); if (vsDisabled in Node.States) or not Enabled then - Canvas.Font.Color := FColors.DisabledColor; + Canvas.Font.Color := Colors.DisabledColor; // Multiline nodes don't need special font handling or text manipulation. // Note: multiline support requires the Unicode version of DrawText, which is able to do word breaking. // The emulation in this unit does not support this so we have to use the OS version. However @@ -33857,7 +864,7 @@ procedure TCustomVirtualStringTree.PaintNormalText(var PaintInfo: TVTPaintInfo; // Center the text vertically if it fits entirely into the content rect. if R.Bottom - R.Top > Height then - InflateRect(R, 0, (Height - R.Bottom - R.Top) div 2); + InflateRect(R, 0, Divide(Height - R.Bottom - R.Top, 2)); end else begin @@ -33869,7 +876,7 @@ procedure TCustomVirtualStringTree.PaintNormalText(var PaintInfo: TVTPaintInfo; // If the font has been changed then the ellipsis width must be recalculated. TripleWidth := 0; // Recalculate also the width of the normal text. - lNewNodeWidth := DoTextMeasuring(Canvas, Node, Column, Text).cx + 2 * FTextMargin; + lNewNodeWidth := DoTextMeasuring(Canvas, Node, Column, Text).cx + 2 * TextMargin; if lNewNodeWidth <> NodeWidth then begin NodeWidth := lNewNodeWidth; @@ -33881,7 +888,7 @@ procedure TCustomVirtualStringTree.PaintNormalText(var PaintInfo: TVTPaintInfo; if BidiMode <> bdLeftToRight then DrawFormat := DrawFormat or DT_RTLREADING; // Check if the text must be shortend. - if (Column > NoColumn) and ((NodeWidth - 2 * FTextMargin) > R.Width) then + if (Column > NoColumn) and ((NodeWidth - 2 * TextMargin) > R.Width) then begin Text := DoShortenString(Canvas, Node, Column, Text, R.Right - R.Left, TripleWidth); if Alignment = taRightJustify then @@ -33915,23 +922,23 @@ procedure TCustomVirtualStringTree.PaintStaticText(const PaintInfo: TVTPaintInfo begin with PaintInfo do begin - Canvas.Font := Font; - if toFullRowSelect in FOptions.SelectionOptions then + Canvas.Font.Assign(Font); + if toFullRowSelect in TreeOptions.SelectionOptions then begin - if Node = FDropTargetNode then + if Node = DropTargetNode then begin - if (FLastDropMode = dmOnNode) or (vsSelected in Node.States) then - Canvas.Font.Color := FColors.GetSelectedNodeFontColor(Focused) + if (LastDropMode = dmOnNode) or (vsSelected in Node.States) then + Canvas.Font.Color := Colors.GetSelectedNodeFontColor(Focused or (toPopupMode in TreeOptions.PaintOptions)) else - Canvas.Font.Color := FColors.NodeFontColor; + Canvas.Font.Color := Colors.NodeFontColor; end else if vsSelected in Node.States then begin - if Focused or (toPopupMode in FOptions.PaintOptions) then - Canvas.Font.Color := FColors.GetSelectedNodeFontColor(Focused) + if Focused or (toPopupMode in TreeOptions.PaintOptions) then + Canvas.Font.Color := Colors.GetSelectedNodeFontColor(Focused or (toPopupMode in TreeOptions.PaintOptions)) else - Canvas.Font.Color := FColors.NodeFontColor; + Canvas.Font.Color := Colors.NodeFontColor; end; end; @@ -33941,17 +948,17 @@ procedure TCustomVirtualStringTree.PaintStaticText(const PaintInfo: TVTPaintInfo // Disabled node color overrides all other variants. if (vsDisabled in Node.States) or not Enabled then - Canvas.Font.Color := FColors.DisabledColor; + Canvas.Font.Color := Colors.DisabledColor; R := ContentRect; if pStaticTextAlignment = taRightJustify then begin DrawFormat := DrawFormat or DT_RIGHT; - Dec(R.Right, FTextMargin); + Dec(R.Right, TextMargin); if PaintInfo.Alignment = taRightJustify then Dec(R.Right, NodeWidth); // room for node text end else begin - Inc(R.Left, FTextMargin); + Inc(R.Left, TextMargin); if PaintInfo.Alignment = taLeftJustify then Inc(R.Left, NodeWidth); // room for node text end; @@ -34012,7 +1019,7 @@ procedure TCustomVirtualStringTree.SetDefaultText(const Value: string); procedure TCustomVirtualStringTree.SetOptions(const Value: TCustomStringTreeOptions); begin - FOptions.Assign(Value); + inherited TreeOptions.Assign(Value); end; //---------------------------------------------------------------------------------------------------------------------- @@ -34044,7 +1051,7 @@ procedure TCustomVirtualStringTree.WMSetFont(var Msg: TWMSetFont); MemDC := CreateCompatibleDC(0); try SelectObject(MemDC, Msg.Font); - GetTextMetrics(MemDC, TM); + WinApi.Windows.GetTextMetrics(MemDC, TM); FTextHeight := TM.tmHeight; GetTextExtentPoint32W(MemDC, '...', 3, Size); @@ -34054,7 +1061,7 @@ procedure TCustomVirtualStringTree.WMSetFont(var Msg: TWMSetFont); end; // Have to reset all node widths. - Run := FRoot.FirstChild; + Run := RootNode.FirstChild; while Assigned(Run) do begin Data := InternalData(Run); @@ -34079,15 +1086,15 @@ function TCustomVirtualStringTree.AddChild(Parent: PVirtualNode; UserData: Point if FPreviouslySelected.IndexOf(NewNodeText) >= 0 then begin // Select this node and make sure that the parent node is expanded - Include(FStates, tsPreviouslySelectedLocked); + TreeStates:= TreeStates + [tsPreviouslySelectedLocked]; try Self.Selected[Result] := True; finally - Exclude(FStates, tsPreviouslySelectedLocked); + TreeStates:= TreeStates - [tsPreviouslySelectedLocked]; end; // if a there is a selected node now, then make sure that it is visible if (Self.GetFirstSelected <> nil) then - Self.SetFullyVisible(Self.GetFirstSelected, True); + Self.FullyVisible[Self.GetFirstSelected]:= True; end; end; end; @@ -34100,8 +1107,8 @@ procedure TCustomVirtualStringTree.AdjustPaintCellRect(var PaintInfo: TVTPaintIn // Note: the autospan feature can only be used with left-to-right layout. begin - if (toAutoSpanColumns in FOptions.AutoOptions) and FHeader.UseColumns and (PaintInfo.BidiMode = bdLeftToRight) then - with FHeader.Columns, PaintInfo do + if (toAutoSpanColumns in TreeOptions.AutoOptions) and Header.UseColumns and (PaintInfo.BidiMode = bdLeftToRight) then + with Header.Columns, PaintInfo do begin // Start with the directly following column. NextNonEmpty := GetNextVisibleColumn(Column); @@ -34123,7 +1130,7 @@ procedure TCustomVirtualStringTree.AdjustPaintCellRect(var PaintInfo: TVTPaintIn //---------------------------------------------------------------------------------------------------------------------- -function TCustomVirtualStringTree.CalculateStaticTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: string): Integer; +function TCustomVirtualStringTree.CalculateStaticTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const Text: string): TDimension; begin Result := 0; @@ -34132,22 +1139,22 @@ function TCustomVirtualStringTree.CalculateStaticTextWidth(Canvas: TCanvas; Node DoPaintText(Node, Canvas, Column, ttStatic); Inc(Result, DoTextMeasuring(Canvas, Node, Column, Text).cx); - Inc(Result, FTextMargin); + Inc(Result, TextMargin); end; end; //---------------------------------------------------------------------------------------------------------------------- function TCustomVirtualStringTree.CalculateTextWidth(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; - const Text: string): Integer; + const Text: string): TDimension; // Determines the width of the given text. begin - Result := 2 * FTextMargin; + Result := 2 * TextMargin; if Length(Text) > 0 then begin - Canvas.Font := Font; + Canvas.Font.Assign(Font); DoPaintText(Node, Canvas, Column, ttNormal); Inc(Result, DoTextMeasuring(Canvas, Node, Column, Text).cx); @@ -34193,8 +1200,30 @@ destructor TCustomVirtualStringTree.Destroy; //---------------------------------------------------------------------------------------------------------------------- -function TCustomVirtualStringTree.DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; +procedure TCustomVirtualStringTree.DoAddToSelection(Node: PVirtualNode); +var + lSelectedNodeCaption: string; +begin + inherited; + if (toRestoreSelection in TreeOptions.SelectionOptions) and Assigned(Self.OnGetText) and not (tsPreviouslySelectedLocked in TreeStates) then + begin + if not Assigned(FPreviouslySelected) then + begin + FPreviouslySelected := TStringList.Create(); + FPreviouslySelected.Duplicates := dupIgnore; + FPreviouslySelected.Sorted := True; //Improves performance, required to use Find() + FPreviouslySelected.CaseSensitive := False; + end; + if Self.SelectedCount = 1 then + FPreviouslySelected.Clear(); + Self.OnGetText(Self, Node, Header.RestoreSelectionColumnIndex, ttNormal, lSelectedNodeCaption); + FPreviouslySelected.Add(lSelectedNodeCaption); + end;//if +end; + +//---------------------------------------------------------------------------------------------------------------------- +function TCustomVirtualStringTree.DoCreateEditor(Node: PVirtualNode; Column: TColumnIndex): IVTEditLink; begin Result := inherited DoCreateEditor(Node, Column); // Enable generic label editing support if the application does not have own editors. @@ -34229,7 +1258,7 @@ function TCustomVirtualStringTree.DoGetNodeTooltip(Node: PVirtualNode; Column: T //---------------------------------------------------------------------------------------------------------------------- function TCustomVirtualStringTree.DoGetNodeExtraWidth(Node: PVirtualNode; Column: TColumnIndex; - Canvas: TCanvas = nil): Integer; + Canvas: TCanvas = nil): TDimension; begin if not (toShowStaticText in TreeOptions.StringOptions) then @@ -34241,23 +1270,23 @@ function TCustomVirtualStringTree.DoGetNodeExtraWidth(Node: PVirtualNode; Column //---------------------------------------------------------------------------------------------------------------------- -function TCustomVirtualStringTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; +function TCustomVirtualStringTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): TDimension; // Returns the text width of the given node in pixels. // This width is stored in the node's data member to increase access speed. var - Data: PInteger; + Data: PDimension; begin if (Column > NoColumn) and (vsMultiline in Node.States) then - Result := FHeader.Columns[Column].Width + Result := Header.Columns[Column].Width else begin if Canvas = nil then Canvas := Self.Canvas; - if (Column = FHeader.MainColumn) or (Column = NoColumn) then + if (Column = Header.MainColumn) or (Column = NoColumn) then begin // Primary column or no columns. Data := InternalData(Node); @@ -34265,7 +1294,7 @@ function TCustomVirtualStringTree.DoGetNodeWidth(Node: PVirtualNode; Column: TCo begin Result := Data^; if (Result = 0) - or FHeader.doingAutoFitColumns then + or Header.doingAutoFitColumns then begin Data^ := CalculateTextWidth(Canvas, Node, Column, Text[Node, Column]); Result := Data^; @@ -34307,8 +1336,8 @@ function TCustomVirtualStringTree.DoIncrementalSearch(Node: PVirtualNode; const begin Result := 0; - if Assigned(FOnIncrementalSearch) then - FOnIncrementalSearch(Self, Node, Text, Result) + if Assigned(OnIncrementalSearch) then + OnIncrementalSearch(Self, Node, Text, Result) else // Default behavior is to match the search string with the start of the node text. if not StartsText(Text, GetText(Node, FocusedColumn)) then @@ -34324,7 +1353,7 @@ procedure TCustomVirtualStringTree.DoNewText(Node: PVirtualNode; Column: TColumn FOnNewText(Self, Node, Column, Text); // The width might have changed, so update the scrollbar. - if FUpdateCount = 0 then + if UpdateCount = 0 then UpdateHorizontalScrollBar(True); end; @@ -34367,19 +1396,8 @@ procedure TCustomVirtualStringTree.DoPaintNode(var PaintInfo: TVTPaintInfo); //---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomVirtualStringTree.DoPaintText(Node: PVirtualNode; const Canvas: TCanvas; Column: TColumnIndex; - TextType: TVSTTextType); - -begin - if Assigned(FOnPaintText) then - FOnPaintText(Self, Canvas, Node, Column, TextType); -end; - -//---------------------------------------------------------------------------------------------------------------------- - function TCustomVirtualStringTree.DoShortenString(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; - const S: string; Width: Integer; EllipsisWidth: Integer = 0): string; + const S: string; Width: TDimension; EllipsisWidth: TDimension = 0): string; var Done: Boolean; @@ -34454,6 +1472,8 @@ function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer; begin if (Node = nil) or (FInternalDataOffset = 0) then Result := nil + else if Node = RootNode then + Result := PByte(Node) + FInternalDataOffset else Result := PByte(Node) + Self.NodeDataSize + FInternalDataOffset; end; @@ -34470,7 +1490,7 @@ procedure TCustomVirtualStringTree.MainColumnChanged; inherited; // Have to reset all node widths. - Run := FRoot.FirstChild; + Run := RootNode.FirstChild; while Assigned(Run) do begin Data := InternalData(Run); @@ -34501,7 +1521,7 @@ function TCustomVirtualStringTree.ReadChunk(Stream: TStream; Version: Integer; N Stream.Read(PWideChar(NewText)^, ChunkSize); end; // Do a new text event regardless of the caption content to allow removing the default string. - Text[Node, FHeader.MainColumn] := NewText; + Text[Node, Header.MainColumn] := NewText; Result := True; end; else @@ -34591,25 +1611,25 @@ procedure TCustomVirtualStringTree.WriteChunks(Stream: TStream; Node: PVirtualNo // (take out soSaveCaption from StringOptions). Otherwise the caption is unnecessarily stored twice. var - Header: TChunkHeader; + ChunkHeader: TChunkHeader; S: string; Len: Integer; begin inherited; - if (toSaveCaptions in TreeOptions.StringOptions) and (Node <> FRoot) and + if (toSaveCaptions in TreeOptions.StringOptions) and (Node <> RootNode) and (vsInitialized in Node.States) then with Stream do begin // Read the node's caption (primary column only). - S := Text[Node, FHeader.MainColumn]; + S := Text[Node, Header.MainColumn]; Len := 2 * Length(S); if Len > 0 then begin // Write a new sub chunk. - Header.ChunkType := CaptionChunk; - Header.ChunkSize := Len; - Write(Header, SizeOf(Header)); + ChunkHeader.ChunkType := CaptionChunk; + ChunkHeader.ChunkSize := Len; + Write(ChunkHeader, SizeOf(ChunkHeader)); Write(PWideChar(S)^, Len); end; end; @@ -34623,7 +1643,7 @@ procedure TCustomVirtualStringTree.WriteText(Writer: TWriter); //---------------------------------------------------------------------------------------------------------------------- function TCustomVirtualStringTree.ComputeNodeHeight(Canvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; - S: string): Integer; + S: string): TDimension; // Default node height calculation for multi line nodes. This method can be used by the application to delegate the // computation to the string tree. @@ -34670,7 +1690,7 @@ function TCustomVirtualStringTree.ComputeNodeHeight(Canvas: TCanvas; Node: PVirt GetOffsets(Node, lOffsets, TVTElement.ofsEndOfClientArea, Column); if Column > NoColumn then begin - PaintInfo.CellRect.Right := FHeader.Columns[Column].Width - 2 * FTextMargin; + PaintInfo.CellRect.Right := Header.Columns[Column].Width - 2 * TextMargin; PaintInfo.CellRect.Left := lOffsets[TVTElement.ofsLabel]; end else @@ -34721,11 +1741,11 @@ function TCustomVirtualStringTree.ContentToHTML(Source: TVSTTextSourceType; cons function TCustomVirtualStringTree.CanExportNode(Node: PVirtualNode): Boolean; begin - case FOptions.ExportMode of + case TreeOptions.ExportMode of emChecked: - Result := GetCheckState(Node) = csCheckedNormal; + Result := CheckState[Node] = csCheckedNormal; emUnchecked: - Result := GetCheckState(Node) = csUncheckedNormal; + Result := CheckState[Node] = csUncheckedNormal; emVisibleDueToExpansion: //Do not export nodes that are not visible because their parent is not expanded Result := not Assigned(Node.Parent) or Self.Expanded[Node.Parent]; emSelected: // export selected nodes only @@ -34737,54 +1757,6 @@ function TCustomVirtualStringTree.CanExportNode(Node: PVirtualNode): Boolean; //---------------------------------------------------------------------------------------------------------------------- -procedure TCustomVirtualStringTree.AddToSelection(Node: PVirtualNode; NotifySynced: Boolean); -var - lSelectedNodeCaption: string; -begin - inherited; - if (toRestoreSelection in TreeOptions.SelectionOptions) and Assigned(Self.OnGetText) and Self.Selected[Node] and not (tsPreviouslySelectedLocked in FStates) then - begin - if not Assigned(FPreviouslySelected) then - begin - FPreviouslySelected := TStringList.Create(); - FPreviouslySelected.Duplicates := dupIgnore; - FPreviouslySelected.Sorted := True; //Improves performance, required to use Find() - FPreviouslySelected.CaseSensitive := False; - end; - if Self.SelectedCount = 1 then - FPreviouslySelected.Clear(); - Self.OnGetText(Self, Node, Header.RestoreSelectionColumnIndex, ttNormal, lSelectedNodeCaption); - FPreviouslySelected.Add(lSelectedNodeCaption); - end;//if -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomVirtualStringTree.AddToSelection(const NewItems: TNodeArray; NewLength: Integer; ForceInsert: Boolean); -var - i: Integer; - lSelectedNodeCaption: string; -begin - if (toRestoreSelection in TreeOptions.SelectionOptions) and Assigned(Self.OnGetText) and not (tsPreviouslySelectedLocked in FStates) then - begin - if not Assigned(FPreviouslySelected) then - begin - FPreviouslySelected := TStringList.Create(); - FPreviouslySelected.Duplicates := dupIgnore; - FPreviouslySelected.Sorted := True; //Improves performance, required to use Find() - FPreviouslySelected.CaseSensitive := False; - end; - for i := 0 to NewLength - 1 do - begin - Self.OnGetText(Self, NewItems[i], Header.RestoreSelectionColumnIndex, ttNormal, lSelectedNodeCaption); - FPreviouslySelected.Add(lSelectedNodeCaption); - end; - end; // if toRestoreSelection - inherited; -end; - -//---------------------------------------------------------------------------------------------------------------------- - procedure TCustomVirtualStringTree.RemoveFromSelection(Node: PVirtualNode); var lSelectedNodeCaption: string; @@ -34862,14 +1834,14 @@ procedure TCustomVirtualStringTree.GetTextInfo(Node: PVirtualNode; Column: TColu // bounding rectangle around Text. var - NewHeight: Integer; + NewHeight: TDimension; TM: TTextMetric; begin // Get default font and initialize the other parameters. inherited GetTextInfo(Node, Column, AFont, R, Text); - Canvas.Font := AFont; + Canvas.Font.Assign(AFont); FFontChanged := False; RedirectFontChangeEvent(Canvas); @@ -34877,7 +1849,7 @@ procedure TCustomVirtualStringTree.GetTextInfo(Node: PVirtualNode; Column: TColu if FFontChanged then begin AFont.Assign(Canvas.Font); - GetTextMetrics(Canvas.Handle, TM); + GetTextMetrics(Canvas, TM); NewHeight := TM.tmHeight; end else // Otherwise the correct font is already there and we only need to set the correct height. @@ -34889,7 +1861,7 @@ procedure TCustomVirtualStringTree.GetTextInfo(Node: PVirtualNode; Column: TColu R := GetDisplayRect(Node, Column, True, not (vsMultiline in Node.States)); if toShowHorzGridLines in TreeOptions.PaintOptions then Dec(R.Bottom); - InflateRect(R, 0, -(R.Bottom - R.Top - NewHeight) div 2); + InflateRect(R, 0, -Divide(R.Bottom - R.Top - NewHeight, 2)); end; //---------------------------------------------------------------------------------------------------------------------- @@ -34920,12 +1892,12 @@ function TCustomVirtualStringTree.Path(Node: PVirtualNode; Column: TColumnIndex; // given delimiter. begin - if (Node = nil) or (Node = FRoot) then + if (Node = nil) or (Node = RootNode) then Result := Delimiter else begin Result := ''; - while Node <> FRoot do + while Node <> RootNode do begin Result := Text[Node, Column] + Delimiter + Result; Node := Node.Parent; @@ -34943,7 +1915,7 @@ procedure TCustomVirtualStringTree.ResetInternalData(Node: PVirtualNode; Recursi begin // Reset node width so changed text attributes are applied correctly. - if Assigned(Node) and (Node <> FRoot) then + if Assigned(Node) and (Node <> RootNode) then begin Data := InternalData(Node); if Assigned(Data) then @@ -34955,7 +1927,7 @@ procedure TCustomVirtualStringTree.ResetInternalData(Node: PVirtualNode; Recursi if Assigned(Node) then Run := Node.FirstChild else - Run := FRoot.FirstChild; + Run := RootNode.FirstChild; while Assigned(Run) do begin @@ -34967,7 +1939,7 @@ procedure TCustomVirtualStringTree.ResetInternalData(Node: PVirtualNode; Recursi //---------------------------------------------------------------------------------------------------------------------- -procedure TCustomVirtualStringTree.ReinitNode(Node: PVirtualNode; Recursive: Boolean); +procedure TCustomVirtualStringTree.ReinitNode(Node: PVirtualNode; Recursive: Boolean; ForceReinit: Boolean = False); begin inherited; @@ -34990,7 +1962,7 @@ procedure TCustomVirtualStringTree.SetChildCount(Node: PVirtualNode; NewChildCou function TVirtualStringTree.GetOptions: TStringTreeOptions; begin - Result := FOptions as TStringTreeOptions; + Result := inherited TreeOptions as TStringTreeOptions; end; //---------------------------------------------------------------------------------------------------------------------- @@ -34998,7 +1970,7 @@ function TVirtualStringTree.GetOptions: TStringTreeOptions; procedure TVirtualStringTree.SetOptions(const Value: TStringTreeOptions); begin - FOptions.Assign(Value); + inherited TreeOptions.Assign(Value); end; //---------------------------------------------------------------------------------------------------------------------- @@ -35009,149 +1981,10 @@ function TVirtualStringTree.GetOptionsClass: TTreeOptionsClass; Result := TStringTreeOptions; end; -//---------------------------------------------------------------------------------------------------------------------- - -function TCustomVirtualDrawTree.DoGetCellContentMargin(Node: PVirtualNode; Column: TColumnIndex; - CellContentMarginType: TVTCellContentMarginType = ccmtAllSides; Canvas: TCanvas = nil): TPoint; - -begin - Result := Point(0, 0); - if Canvas = nil then - Canvas := Self.Canvas; - - if Assigned(FOnGetCellContentMargin) then - FOnGetCellContentMargin(Self, Canvas, Node, Column, CellContentMarginType, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TCustomVirtualDrawTree.DoGetNodeWidth(Node: PVirtualNode; Column: TColumnIndex; Canvas: TCanvas = nil): Integer; - -begin - Result := 2 * FTextMargin; - if Canvas = nil then - Canvas := Self.Canvas; - - if Assigned(FOnGetNodeWidth) then - FOnGetNodeWidth(Self, Canvas, Node, Column, Result); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TCustomVirtualDrawTree.DoPaintNode(var PaintInfo: TVTPaintInfo); - -begin - if Assigned(FOnDrawNode) then - FOnDrawNode(Self, PaintInfo); -end; - -function TCustomVirtualDrawTree.GetDefaultHintKind: TVTHintKind; - -begin - Result := vhkOwnerDraw; -end; - -//----------------- TVirtualDrawTree ----------------------------------------------------------------------------------- - -function TVirtualDrawTree.GetOptions: TVirtualTreeOptions; - -begin - Result := FOptions as TVirtualTreeOptions; -end; - -//---------------------------------------------------------------------------------------------------------------------- - -procedure TVirtualDrawTree.SetOptions(const Value: TVirtualTreeOptions); - -begin - FOptions.Assign(Value); -end; - -//---------------------------------------------------------------------------------------------------------------------- - -function TVirtualDrawTree.GetOptionsClass: TTreeOptionsClass; - -begin - Result := TVirtualTreeOptions; -end; - +{ TVSTGetCellTextEventArgs } //---------------------------------------------------------------------------------------------------------------------- -{ PVirtualNodeHelper } - -function TVirtualNode.GetData(): Pointer; - -// Returns the associated data converted to the class given in the generic part of the function. - -begin - Result := @Self.Data; - Include(States, vsOnFreeNodeCallRequired); -end; - -function TVirtualNode.GetData: T; - -// Returns the associated data converted to the class given in the generic part of the function. - -begin - Result := T(Pointer((PByte(@(Self.Data))))^); - Include(States, vsOnFreeNodeCallRequired); -end; - -function TVirtualNode.IsAssigned: Boolean; - -// Returns False if this node is nil, True otherwise - -begin - Exit(@Self <> nil); -end; - -procedure TVirtualNode.SetData(pUserData: Pointer); - - - // Can be used to set user data of a PVirtualNode with the size of a pointer, useful for setting - // A pointer to a record or a reference to a class instance. -var - NodeData: PPointer; -begin - NodeData := PPointer(@Self.Data); - NodeData^ := pUserData; - Include(Self.States, vsOnFreeNodeCallRequired); -end; - -procedure TVirtualNode.SetData(const pUserData: IInterface); - - - // Can be used to set user data of a PVirtualNode to a class instance, - // will take care about reference counting. - -begin - pUserData._AddRef(); - SetData(Pointer(pUserData)); - Include(Self.States, vsReleaseCallOnUserDataRequired); -end; - -procedure TVirtualNode.SetData(pUserData: T); - -begin - T(Pointer((PByte(@(Self.Data))))^) := pUserData; - if PTypeInfo(TypeInfo(T)).Kind = tkInterface then - Include(Self.States, vsReleaseCallOnUserDataRequired); - Include(Self.States, vsOnFreeNodeCallRequired); -end; - -{ TVTImageInfo } - -function TVTImageInfo.Equals(const pImageInfo2: TVTImageInfo): Boolean; - - // Returns true if both images are the same, does not regard Ghosted and position. - -begin - Result := (Self.Index = pImageInfo2.Index) and (Self.Images = pImageInfo2.Images); -end; - -{ TVSTGetCellTextEventArgs } - constructor TVSTGetCellTextEventArgs.Create(pNode: PVirtualNode; pColumn: TColumnIndex; pExportType: TVTExportType); begin Self.Node := pNode; @@ -35159,115 +1992,10 @@ constructor TVSTGetCellTextEventArgs.Create(pNode: PVirtualNode; pColumn: TColum Self.ExportType := pExportType; end; -{ TCheckStateHelper } - -function TCheckStateHelper.IsDisabled: Boolean; -begin - Result := Self >= TCheckState.csUncheckedDisabled; -end; - -function TCheckStateHelper.IsChecked: Boolean; -begin - Result := Self in [csCheckedNormal, csCheckedPressed, csCheckedDisabled]; -end; - -function TCheckStateHelper.IsUnChecked: Boolean; -begin - Result := Self in [csUnCheckedNormal, csUnCheckedPressed, csUnCheckedDisabled]; -end; - -function TCheckStateHelper.IsMixed: Boolean; -begin - Result := Self in [csMixedNormal, csMixedPressed, csMixedDisabled]; -end; - -function TCheckStateHelper.GetEnabled: TCheckState; -begin - Result := cEnabledState[Self]; -end; - -function TCheckStateHelper.GetPressed(): TCheckState; -begin - Result := cPressedState[Self]; -end; - -function TCheckStateHelper.GetUnpressed(): TCheckState; -begin - Result := cUnpressedState[Self]; -end; - -function TCheckStateHelper.GetToggled(): TCheckState; -begin - Result := cToggledState[Self]; -end; - -{ TSortDirectionHelper } - -function TSortDirectionHelper.ToInt(): Integer; -begin - Result := cSortDirectionToInt[Self]; -end; - - -{ TVTPaintInfo } - -procedure TVTPaintInfo.AdjustImageCoordinates(); -// During painting of the main column some coordinates must be adjusted due to the tree lines. -begin - ContentRect := CellRect; - if BidiMode = bdLeftToRight then - begin - ContentRect.Left := CellRect.Left + Offsets[TVTElement.ofsLabel]; - ImageInfo[iiNormal].XPos := CellRect.Left + Offsets[TVTElement.ofsImage]; - ImageInfo[iiState].XPos := CellRect.Left + Offsets[TVTElement.ofsStateImage]; - ImageInfo[iiCheck].XPos := CellRect.Left + Offsets[TVTElement.ofsCheckBox]; - end - else - begin - /// Since images are still drawn from left to right, we need to substract the image sze as well. - ImageInfo[iiNormal].XPos := CellRect.Right - Offsets[TVTElement.ofsImage] - (Offsets[TVTElement.ofsLabel] - Offsets[TVTElement.ofsImage]); - ImageInfo[iiState].XPos := CellRect.Right - Offsets[TVTElement.ofsStateImage] - (Offsets[TVTElement.ofsImage] - Offsets[TVTElement.ofsStateImage]); - ImageInfo[iiCheck].XPos := CellRect.Right - Offsets[TVTElement.ofsCheckBox] - (Offsets[TVTElement.ofsStateImage] - Offsets[TVTElement.ofsCheckBox]); - ContentRect.Right := CellRect.Right - Offsets[TVTElement.ofsLabel]; - end; - if ImageInfo[iiNormal].Index > -1 then - ImageInfo[iiNormal].YPos := CellRect.Top + VAlign - ImageInfo[iiNormal].Images.Height div 2; - if ImageInfo[iiState].Index > -1 then - ImageInfo[iiState].YPos := CellRect.Top + VAlign - ImageInfo[iiState].Images.Height div 2; - if ImageInfo[iiCheck].Index > -1 then - ImageInfo[iiCheck].YPos := CellRect.Top + VAlign - ImageInfo[iiCheck].Images.Height div 2; -end; - -{ THeaderPaintInfo } - -procedure THeaderPaintInfo.DrawDropMark(); -var - Y: Integer; - lArrowWidth: Integer; -begin - lArrowWidth := Self.Column.Owner.Header.Treeview.ScaledPixels(5); - Y := (PaintRectangle.Top + PaintRectangle.Bottom - 3 * lArrowWidth) div 2; - if DropMark = dmmLeft then - DrawArrow(TargetCanvas, TScrollDirection.sdLeft, Point(PaintRectangle.Left, Y), lArrowWidth) - else - DrawArrow(TargetCanvas, TScrollDirection.sdRight, Point(PaintRectangle.Right - lArrowWidth - (lArrowWidth div 2) {spacing}, Y), lArrowWidth); -end; - -procedure THeaderPaintInfo.DrawSortArrow(pDirection: TSortDirection); -const - cDirection: array[TSortDirection] of TScrollDirection = (TScrollDirection.sdUp, TScrollDirection.sdDown); -var - lOldColor: TColor; -begin - lOldColor := TargetCanvas.Pen.Color; - TargetCanvas.Pen.Color := clDkGray; - DrawArrow(TargetCanvas, cDirection[pDirection], Point(SortGlyphPos.X, SortGlyphPos.Y), SortGlyphSize.cy); - TargetCanvas.Pen.Color := lOldColor; -end; - initialization + TCustomStyleEngine.RegisterStyleHook(TVirtualStringTree, TVclStyleScrollBarsHook); finalization - FinalizeGlobalStructures(); + TCustomStyleEngine.UnRegisterStyleHook(TVirtualStringTree, TVclStyleScrollBarsHook); end. diff --git a/components/virtualtreeview/Source/VirtualTrees.res b/components/virtualtreeview/Source/VirtualTrees.res index deb671eba..558fd1ef1 100644 Binary files a/components/virtualtreeview/Source/VirtualTrees.res and b/components/virtualtreeview/Source/VirtualTrees.res differ diff --git a/components/virtualtreeview/Virtual-TreeView.dspec b/components/virtualtreeview/Virtual-TreeView.dspec index a8826551b..2927f96c8 100644 --- a/components/virtualtreeview/Virtual-TreeView.dspec +++ b/components/virtualtreeview/Virtual-TreeView.dspec @@ -1,104 +1,72 @@ { "metadata": { "id": "JAM.VirtualTreeView", - "version": "7.4.0", + "version": "8.1.2", "description": "Virtual TreeView VCL Component", "authors": "Joachim Marder", - "projectUrl": "https://github.com/Virtual-TreeView/Virtual-TreeView", + "projectUrl": "https://github.com/JAM-Software/Virtual-TreeView", "license": "MPL-1.1, LGPL-2.0+", "copyright": "Various, See project page", "tags": "VCL, TreeView" }, "targetPlatforms": [ { - "compiler": "XE3", - "platforms": "Win32, Win64", - "template": "default", - "variables" : { - "libsuffix" : "17" - } - }, - { - "compiler": "XE4", - "platforms": "Win32, Win64", - "template": "default", - "variables" : { - "libsuffix" : "18" - } - }, - { - "compiler": "XE5", - "platforms": "Win32, Win64", - "template": "default", - "variables" : { - "libsuffix" : "19" - } - }, - { - "compiler": "XE6", - "platforms": "Win32, Win64", - "template": "default", - "variables" : { - "libsuffix" : "20" - } - }, - { - "compiler": "XE7", + "compiler": "10.0", "platforms": "Win32, Win64", "template": "default", "variables" : { - "libsuffix" : "21" + "libsuffix" : "230", + "compiler" : "$compilerNoPoint$" } }, { - "compiler": "XE8", + "compiler": "10.1", "platforms": "Win32, Win64", "template": "default", "variables" : { - "libsuffix" : "22" + "libsuffix" : "240" } }, { - "compiler": "10.0", + "compiler": "10.2", "platforms": "Win32, Win64", "template": "default", "variables" : { - "libsuffix" : "23", - "compiler" : "$compilerNoPoint$" + "libsuffix" : "250" } }, { - "compiler": "10.1", + "compiler": "10.3", "platforms": "Win32, Win64", "template": "default", "variables" : { - "libsuffix" : "24" + "libsuffix" : "260" } }, { - "compiler": "10.2", + "compiler": "10.4", "platforms": "Win32, Win64", - "template": "default", + "template": "10.4+", "variables" : { - "libsuffix" : "25" + "libsuffix" : "270" } }, { - "compiler": "10.3", + "compiler": "11.0", "platforms": "Win32, Win64", - "template": "default", + "template": "10.4+", "variables" : { - "libsuffix" : "26" + "libsuffix" : "280" } - }, + } { - "compiler": "10.4", + "compiler": "12.0", "platforms": "Win32, Win64", - "template": "default", + "template": "10.4+", "variables" : { - "libsuffix" : "27" + "libsuffix" : "290" } - } + } ], "templates": [ { @@ -157,6 +125,63 @@ "install": true } ] + }, + { + "comment": "10.4+ versions share packages", + "name": "10.4+", + "source": [ + { + "src": ".\\Source\\*.pas", + "dest": "Source" + }, + { + "src": ".\\Source\\*.res", + "dest": "Source" + }, + { + "src": ".\\Design\\*.*", + "dest": "Design" + }, + { + "src": ".\\packages\\Rad Studio 10.4+\\**", + "flatten": false, + "dest": "packages\\Rad Studio $compiler$" + } + ], + "searchPaths": [ + { + "path": "Source", + "source": true + } + ], + "build": [ + { + "id": "VirtualTreesR", + "project": ".\\Packages\\Rad Studio $compiler$\\VirtualTreesR.dproj", + "buildForDesign": true, + "buildForDesignComment" : "when true, will also build win32 if the platform is not win32, so that other packages that need this for design will work" + }, + { + "id": "VirtualTreesD", + "project": ".\\Packages\\Rad Studio $compiler$\\VirtualTreesD.dproj", + "designOnly" : true, + "designOnlyComment" : "designOnly forces compilation with win32 compiler" + } + ], + "runtime": [ + { + "buildId": "VirtualTreesR", + "src": "bin\\VirtualTreesR$libsuffix$.bpl", + "copyLocal": true + } + ], + "design": [ + { + "buildId": "VirtualTreesD", + "src": "bin\\VirtualTreesD$libsuffix$.bpl", + "install": true + } + ] } ] } diff --git a/components/virtualtreeview/packages/Delphi11.1/VirtualTreesD.dproj b/components/virtualtreeview/packages/Delphi11.1/VirtualTreesD.dproj deleted file mode 100644 index f823ce642..000000000 --- a/components/virtualtreeview/packages/Delphi11.1/VirtualTreesD.dproj +++ /dev/null @@ -1,127 +0,0 @@ - - - True - Package - Release - DCC32 - VCL - VirtualTreesD.dpk - Win32 - {A34BA07B-19B6-4C21-9DEE-65FCA52D00AB} - 19.4 - 1 - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - VirtualTreesD - All - ..\..\build\$(Platform) - VirtualTreeView Controls - ..\..\Source - 00400000 - System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) - true - ..\..\source;.\$(Platform)\$(Config);$(DCC_UnitSearchPath) - true - true - true - CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= - 1053 - - - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - vcl;VirtualTreesR;$(DCC_UsePackage) - $(BDS)\BIN\Bds.exe - - - RELEASE;$(DCC_Define) - - - DEBUG;$(DCC_Define) - true - false - - - - MainSource - - - - - - - Base - - - Cfg_1 - Base - - - Cfg_2 - Base - - - - Delphi.Personality.12 - Package - - - - VirtualTreesD.dpk - - - - True - False - 1 - 0 - 0 - 0 - False - False - False - False - False - 1053 - 1252 - - - - - 1.0.0.0 - - - - - - 1.0.0.0 - - - - - True - False - - - 12 - - - - diff --git a/components/virtualtreeview/packages/Delphi11.2/VirtualTreeView.groupproj b/components/virtualtreeview/packages/Delphi11.2/VirtualTreeView.groupproj deleted file mode 100644 index b0f33e510..000000000 --- a/components/virtualtreeview/packages/Delphi11.2/VirtualTreeView.groupproj +++ /dev/null @@ -1,48 +0,0 @@ - - - {CC6A9541-DD5C-4BCD-8914-016D8D2EAB3B} - - - - - - - VirtualTreesR.dproj - - - - Default.Personality.12 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/components/virtualtreeview/packages/Delphi11.2/VirtualTreesD.dpk b/components/virtualtreeview/packages/Delphi11.2/VirtualTreesD.dpk deleted file mode 100644 index f5b6e519b..000000000 --- a/components/virtualtreeview/packages/Delphi11.2/VirtualTreesD.dpk +++ /dev/null @@ -1,40 +0,0 @@ -package VirtualTreesD; - -{$R *.res} -{$R '..\..\Design\VirtualTrees.dcr'} -{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} -{$ALIGN 8} -{$ASSERTIONS ON} -{$BOOLEVAL OFF} -{$DEBUGINFO OFF} -{$EXTENDEDSYNTAX ON} -{$IMPORTEDDATA ON} -{$IOCHECKS ON} -{$LOCALSYMBOLS ON} -{$LONGSTRINGS ON} -{$OPENSTRINGS ON} -{$OPTIMIZATION ON} -{$OVERFLOWCHECKS OFF} -{$RANGECHECKS OFF} -{$REFERENCEINFO ON} -{$SAFEDIVIDE OFF} -{$STACKFRAMES OFF} -{$TYPEDADDRESS OFF} -{$VARSTRINGCHECKS ON} -{$WRITEABLECONST OFF} -{$MINENUMSIZE 1} -{$IMAGEBASE $400000} -{$DEFINE RELEASE} -{$ENDIF IMPLICITBUILDING} -{$DESCRIPTION 'VirtualTreeView Controls'} -{$DESIGNONLY} -{$IMPLICITBUILD OFF} - -requires - DesignIDE, - VirtualTreesR; - -contains - VirtualTreesReg in '..\..\Design\VirtualTreesReg.pas'; - -end. diff --git a/components/virtualtreeview/packages/Delphi11.2/VirtualTreesR.dpk b/components/virtualtreeview/packages/Delphi11.2/VirtualTreesR.dpk deleted file mode 100644 index 2c552093e..000000000 --- a/components/virtualtreeview/packages/Delphi11.2/VirtualTreesR.dpk +++ /dev/null @@ -1,49 +0,0 @@ -package VirtualTreesR; - -{$R *.res} -{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} -{$ALIGN 8} -{$ASSERTIONS ON} -{$BOOLEVAL OFF} -{$DEBUGINFO OFF} -{$EXTENDEDSYNTAX ON} -{$IMPORTEDDATA ON} -{$IOCHECKS ON} -{$LOCALSYMBOLS ON} -{$LONGSTRINGS ON} -{$OPENSTRINGS ON} -{$OPTIMIZATION ON} -{$OVERFLOWCHECKS OFF} -{$RANGECHECKS OFF} -{$REFERENCEINFO OFF} -{$SAFEDIVIDE OFF} -{$STACKFRAMES OFF} -{$TYPEDADDRESS OFF} -{$VARSTRINGCHECKS ON} -{$WRITEABLECONST OFF} -{$MINENUMSIZE 1} -{$IMAGEBASE $400000} -{$DEFINE RELEASE} -{$ENDIF IMPLICITBUILDING} -{$RUNONLY} -{$IMPLICITBUILD OFF} - -requires - vcl, - vclx; - -contains - VirtualTrees in '..\..\Source\VirtualTrees.pas', - VirtualTrees.HeaderPopup in '..\..\Source\VirtualTrees.HeaderPopup.pas', - VirtualTrees.AccessibilityFactory in '..\..\Source\VirtualTrees.AccessibilityFactory.pas', - VirtualTrees.Accessibility in '..\..\Source\VirtualTrees.Accessibility.pas', - VirtualTrees.StyleHooks in '..\..\Source\VirtualTrees.StyleHooks.pas', - VirtualTrees.Classes in '..\..\Source\VirtualTrees.Classes.pas', - VirtualTrees.WorkerThread in '..\..\Source\VirtualTrees.WorkerThread.pas', - VirtualTrees.ClipBoard in '..\..\Source\VirtualTrees.ClipBoard.pas', - VirtualTrees.Actions in '..\..\Source\VirtualTrees.Actions.pas', - VirtualTrees.Export in '..\..\Source\VirtualTrees.Export.pas', - VirtualTrees.Utils in '..\..\Source\VirtualTrees.Utils.pas', - VirtualTrees.Types in '..\..\Source\VirtualTrees.Types.pas'; - -end. diff --git a/components/virtualtreeview/packages/Delphi11.2/VirtualTreesR.dproj b/components/virtualtreeview/packages/Delphi11.2/VirtualTreesR.dproj deleted file mode 100644 index 5db86ff96..000000000 --- a/components/virtualtreeview/packages/Delphi11.2/VirtualTreesR.dproj +++ /dev/null @@ -1,137 +0,0 @@ - - - True - Package - Release - DCC32 - VCL - VirtualTreesR.dpk - Win64 - {B62F3689-96E1-47D5-9FB2-2A2718281FDB} - 19.5 - 3 - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - VirtualTreesR - All - ..\..\build\$(Platform) - ..\..\Source - 00400000 - System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) - true - ..\..\source;.\$(Platform)\$(Config);$(DCC_UnitSearchPath) - true - true - true - CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= - 1053 - - - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - - - 0 - RELEASE;$(DCC_Define) - false - 0 - - - DEBUG;$(DCC_Define) - true - false - - - - MainSource - - - - - - - - - - - - - - - - - Base - - - Cfg_1 - Base - - - Cfg_2 - Base - - - - Delphi.Personality.12 - Package - - - - VirtualTreesR.dpk - - - - True - False - 1 - 0 - 0 - 0 - False - False - False - False - False - 1053 - 1252 - - - - - 1.0.0.0 - - - - - - 1.0.0.0 - - - - - True - True - - - 12 - - - - diff --git a/components/virtualtreeview/packages/Delphi11.1/VirtualTreeView.groupproj b/components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreeView.groupproj similarity index 100% rename from components/virtualtreeview/packages/Delphi11.1/VirtualTreeView.groupproj rename to components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreeView.groupproj diff --git a/components/virtualtreeview/packages/Delphi11.1/VirtualTreesD.dpk b/components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesD.dpk similarity index 97% rename from components/virtualtreeview/packages/Delphi11.1/VirtualTreesD.dpk rename to components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesD.dpk index f5b6e519b..fa453e88d 100644 --- a/components/virtualtreeview/packages/Delphi11.1/VirtualTreesD.dpk +++ b/components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesD.dpk @@ -27,6 +27,7 @@ package VirtualTreesD; {$DEFINE RELEASE} {$ENDIF IMPLICITBUILDING} {$DESCRIPTION 'VirtualTreeView Controls'} +{$LIBSUFFIX AUTO} {$DESIGNONLY} {$IMPLICITBUILD OFF} diff --git a/components/virtualtreeview/packages/Delphi11.2/VirtualTreesD.dproj b/components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesD.dproj similarity index 81% rename from components/virtualtreeview/packages/Delphi11.2/VirtualTreesD.dproj rename to components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesD.dproj index 0dcd41cc5..337db6336 100644 --- a/components/virtualtreeview/packages/Delphi11.2/VirtualTreesD.dproj +++ b/components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesD.dproj @@ -8,8 +8,9 @@ VirtualTreesD.dpk Win32 {A34BA07B-19B6-4C21-9DEE-65FCA52D00AB} - 19.5 - 1 + 20.3 + 1048577 + VirtualTreesD true @@ -19,6 +20,11 @@ Base true + + true + Base + true + true Base @@ -32,7 +38,7 @@ VirtualTreesD All - ..\..\build\$(Platform) + .\$(Platform)\$(Config) VirtualTreeView Controls ..\..\Source 00400000 @@ -40,6 +46,7 @@ true ..\..\source;.\$(Platform)\$(Config);$(DCC_UnitSearchPath) true + $(Auto) true true CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= @@ -50,6 +57,14 @@ vcl;VirtualTreesR;$(DCC_UsePackage) $(BDS)\BIN\Bds.exe + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + true + RELEASE;$(DCC_Define) @@ -118,6 +133,7 @@ True False + True 12 diff --git a/components/virtualtreeview/packages/Delphi11.1/VirtualTreesR.dpk b/components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesR.dpk similarity index 86% rename from components/virtualtreeview/packages/Delphi11.1/VirtualTreesR.dpk rename to components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesR.dpk index bae6130d7..f65fc3b6e 100644 --- a/components/virtualtreeview/packages/Delphi11.1/VirtualTreesR.dpk +++ b/components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesR.dpk @@ -25,6 +25,7 @@ package VirtualTreesR; {$IMAGEBASE $400000} {$DEFINE RELEASE} {$ENDIF IMPLICITBUILDING} +{$LIBSUFFIX AUTO} {$RUNONLY} {$IMPLICITBUILD OFF} @@ -48,9 +49,13 @@ contains VirtualTrees.Header in '..\..\Source\VirtualTrees.Header.pas', VirtualTrees.HeaderPopup in '..\..\Source\VirtualTrees.HeaderPopup.pas', VirtualTrees in '..\..\source\VirtualTrees.pas', + VirtualTrees.BaseTree in '..\..\source\VirtualTrees.BaseTree.pas', + VirtualTrees.AncestorVCL in '..\..\source\VirtualTrees.AncestorVCL.pas', + VirtualTrees.BaseAncestorVCL in '..\..\source\VirtualTrees.BaseAncestorVCL.pas', VirtualTrees.StyleHooks in '..\..\Source\VirtualTrees.StyleHooks.pas', VirtualTrees.Types in '..\..\Source\VirtualTrees.Types.pas', VirtualTrees.Utils in '..\..\Source\VirtualTrees.Utils.pas', VirtualTrees.WorkerThread in '..\..\Source\VirtualTrees.WorkerThread.pas'; end. + diff --git a/components/virtualtreeview/packages/Delphi11.1/VirtualTreesR.dproj b/components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesR.dproj similarity index 81% rename from components/virtualtreeview/packages/Delphi11.1/VirtualTreesR.dproj rename to components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesR.dproj index ff0f94cbb..c0abab669 100644 --- a/components/virtualtreeview/packages/Delphi11.1/VirtualTreesR.dproj +++ b/components/virtualtreeview/packages/RAD Studio 10.4+/VirtualTreesR.dproj @@ -6,10 +6,11 @@ DCC32 VCL VirtualTreesR.dpk - Win32 + Win64 {B62F3689-96E1-47D5-9FB2-2A2718281FDB} - 19.4 - 3 + 20.3 + 1048579 + VirtualTreesR true @@ -19,6 +20,11 @@ Base true + + true + Base + true + true Base @@ -32,12 +38,13 @@ VirtualTreesR All - ..\..\build\$(Platform) + .\$(Platform)\$(Config) ..\..\Source 00400000 System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) true ..\..\source;.\$(Platform)\$(Config);$(DCC_UnitSearchPath) + $(Auto) true true true @@ -47,6 +54,14 @@ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + true + 0 RELEASE;$(DCC_Define) @@ -79,6 +94,9 @@ + + + @@ -135,6 +153,7 @@ True True + True 12 diff --git a/extra/functions-ini-generator.php b/extra/functions-ini-generator.php new file mode 100644 index 000000000..8123a10f8 --- /dev/null +++ b/extra/functions-ini-generator.php @@ -0,0 +1,287 @@ + '"', + '”' => '"', + '—' => '-', + ' ' => ' ', + ]; + $numVersions = []; + foreach($iniEntries as $iniEntry) { + if(!isset($numVersions[$iniEntry['name']])) + $numVersions[$iniEntry['name']] = 0; + $numVersions[$iniEntry['name']]++; + } + // var_dump($numVersions); + + $sections = $finalEntries = []; + foreach($iniEntries as $iniEntry) { + if($numVersions[$iniEntry['name']] > 1) { + for($i=1; $i<100; $i++) { + $section = $iniEntry['name'] . $i; + if(!in_array($section, $sections)) { + $sections[] = $section; + break; + } + } + } + else { + $section = $iniEntry['name']; + $sections[] = $section; + } + $entry = "[".$section."]".NL; + if($section != $iniEntry['name']) { + $entry .= "name=".$iniEntry['name'].NL; + } + $descr = $iniEntry['description']; + $descr = strtr($descr, $replaceMap); + // Limit description to 50 lines, if the rest is longer than 100 chars + $numLinebreaks = 0; + $lenDescr = strlen($descr); + for($i=0; $i $i + 100) { + $descr = substr($descr, 0, $i+1) . ' ...'; + break; + } + } + //die(); + if($doWordWrap) { + $descr = wordwrap($descr); + } + $descr = str_replace(["\r\n", "\r", "\n"], '\n', $descr); + + $entry .= "declaration=".$iniEntry['declaration'].NL + . "category=".$iniEntry['category'].NL + . "description=".html_entity_decode($descr); + $finalEntries[$section] = $entry; + } + ksort($finalEntries); + //var_dump($finalEntries); + return implode(NL, $finalEntries); +} + + +function gen_sqlite(): string +{ + $urls = [ + 'Aggregate Functions'=>'https://www.sqlite.org/lang_aggfunc.html', + 'Scalar SQL Functions'=>'https://www.sqlite.org/lang_corefunc.html', + 'Window Functions' =>'https://www.sqlite.org/windowfunctions.html', + ]; + + $iniEntries = []; + + foreach($urls as $category=>$url) { + //echo $url."\n"; + $contents = file_get_contents($url); + /* + *

sum(X)
total(X)

+ some text

+

some text

+

some text +

+ */ + preg_match_all('#\(.+)\\s*\(.+)\#isU', $contents, $matches); + //var_dump($matches); + for($i=0; $i', "\n", $matches[1][$i])); + //echo $defs."\n\n"; + $defs = explode("\n", $defs); + foreach($defs as $def) { + if(!preg_match('#^(\w+)\(([^\)]*)\)#', $def, $matchesDef)) { + continue; + } + //var_dump($matchesDef); + $entry = ['name'=>strtoupper($matchesDef[1]), 'declaration'=>$matchesDef[2], 'category'=>$category]; + $descr = $matches[2][$i]; + $descr = preg_replace('#\\s*\#', '\\n', $descr); + $descr = strip_tags($descr); + $descr = preg_replace('#\s+#', ' ', $descr); + $entry['description'] = trim($descr); + //var_dump($entry); + //break(2); + $iniEntries[] = $entry; + } + } + //break; + } + + /* + * non-parsable date functions: + date(time-value, modifier, modifier, ...) + time(time-value, modifier, modifier, ...) + datetime(time-value, modifier, modifier, ...) + julianday(time-value, modifier, modifier, ...) + strftime(format, time-value, modifier, modifier, ...) + */ + $iniEntries[] = [ + 'name'=>'DATE', + 'declaration'=>'time-value, modifier, modifier, ...', + 'category'=>'Date And Time Functions', + 'description'=>'All five date and time functions take a time value as an argument. The time value is followed by zero or more modifiers. The strftime() function also takes a format string as its first argument.', + ]; + $iniEntries[] = [ + 'name'=>'TIME', + 'declaration'=>'time-value, modifier, modifier, ...', + 'category'=>'Date And Time Functions', + 'description'=>'All five date and time functions take a time value as an argument. The time value is followed by zero or more modifiers. The strftime() function also takes a format string as its first argument.', + ]; + $iniEntries[] = [ + 'name'=>'DATETIME', + 'declaration'=>'time-value, modifier, modifier, ...', + 'category'=>'Date And Time Functions', + 'description'=>'All five date and time functions take a time value as an argument. The time value is followed by zero or more modifiers. The strftime() function also takes a format string as its first argument.', + ]; + $iniEntries[] = [ + 'name'=>'JULIANDAY', + 'declaration'=>'time-value, modifier, modifier, ...', + 'category'=>'Date And Time Functions', + 'description'=>'All five date and time functions take a time value as an argument. The time value is followed by zero or more modifiers. The strftime() function also takes a format string as its first argument.', + ]; + $iniEntries[] = [ + 'name'=>'STRFTIME', + 'declaration'=>'format, time-value, modifier, modifier, ...', + 'category'=>'Date And Time Functions', + 'description'=>'All five date and time functions take a time value as an argument. The time value is followed by zero or more modifiers. The strftime() function also takes a format string as its first argument.', + ]; + + return finalizeEntries($iniEntries, true); +} + + +function gen_mysql(int $port) +{ + // Insert your custom password and port + $mysqli = mysqli_connect('localhost', 'root', null, null, $port); + $query = mysqli_query($mysqli, "SELECT t.name, t.description, c.name AS categ + FROM mysql.help_topic t, mysql.help_category c + WHERE + t.help_category_id = c.help_category_id + AND c.name NOT LIKE 'Internal%' + -- and t.name like 'CURRENT_TIMESTAMP' + ORDER BY t.name"); + if(mysqli_errno($mysqli)) { + die ('MySQL connection error: '.mysqli_error($mysqli)); + } + $iniEntries = []; + + while($row = mysqli_fetch_object($query)) { + $name = $row->name; + // Exclude function names with spaces, or other non-word characters: + if(!preg_match('#^\w+$#', $name)) { + //echo "10\n"; + continue; + } + #echo $name."\n"; + $matchCount = preg_match( + '#\b'.preg_quote($row->name).'\s?\[?\(([^\)]*)\)[^\r\n]*[\r\n](.*)$#is', + $row->description, + $matches); + if(!$matchCount) { + //echo "20\n"; + continue; + } + $declaration = trim($matches[1]); + $declaration = preg_replace('#[\r\n]#', ' ', $declaration); + + $description = trim($matches[2]); + if(preg_match('#Description\s+\-+[\r\n](.+)#is', $description, $matchesD)) { + $description = trim($matchesD[1]); + } + //$description = preg_replace('#[\r\n]#', ' ', $description); + #echo $row->name."\n".$matches[2]."\n".$matches[3]."\n\n"; + $iniEntries[] = [ + 'name'=>$row->name, + 'declaration'=>$declaration, + 'category'=>$row->categ, + 'description'=>$description, + ]; + + } + return finalizeEntries($iniEntries, false); +} + + +function gen_pg(): string +{ + /* + * https://www.postgresql.org/docs/current/functions-string.html + * + * + +

ascii ( text ) → integer

+

Returns the numeric code of the first character of the argument. In UTF8 encoding, returns the Unicode code point of the character. In other multibyte encodings, the argument must be an ASCII character.

+

ascii('x')120

+ + */ + static $categoryUrls = [ + 'Numeric/Math Functions' => 'https://www.postgresql.org/docs/current/functions-math.html', + 'String Functions' => 'https://www.postgresql.org/docs/current/functions-string.html', + 'Binary String Functions' => 'https://www.postgresql.org/docs/current/functions-binarystring.html', + 'Bit String Functions' => 'https://www.postgresql.org/docs/current/functions-bitstring.html', + 'Date/Time Functions' => 'https://www.postgresql.org/docs/current/functions-datetime.html', + 'Enum Support Functions' => 'https://www.postgresql.org/docs/current/functions-enum.html', + 'Geometric Functions' => 'https://www.postgresql.org/docs/current/functions-geometry.html', + 'Network Address Functions' => 'https://www.postgresql.org/docs/current/functions-net.html', + 'Text Search Functions' => 'https://www.postgresql.org/docs/current/functions-textsearch.html', + 'JSON Functions' => 'https://www.postgresql.org/docs/current/functions-json.html', + 'Sequence Manipulation Functions' => 'https://www.postgresql.org/docs/current/functions-sequence.html', + 'Array Functions' => 'https://www.postgresql.org/docs/current/functions-array.html', + 'Range Functions' => 'https://www.postgresql.org/docs/current/functions-range.html', + 'Aggregate Functions' => 'https://www.postgresql.org/docs/current/functions-aggregate.html', + 'Window Functions' => 'https://www.postgresql.org/docs/current/functions-window.html', + 'Merge Support Functions' => 'https://www.postgresql.org/docs/current/functions-merge-support.html', + 'Session Information Functions' => 'https://www.postgresql.org/docs/current/functions-info.html', + 'System Administration Functions' => 'https://www.postgresql.org/docs/current/functions-admin.html', + 'Trigger Functions' => 'https://www.postgresql.org/docs/current/functions-trigger.html', + 'Statistics Information Functions' => 'https://www.postgresql.org/docs/current/functions-statistics.html', + ]; + + $iniEntries = []; + foreach($categoryUrls as $category => $url) { + $doc = file_get_contents($url); + if(empty($doc)) { + throw new RuntimeException("Could not read $url"); + } + $numMatches = preg_match_all('#

]*>\s*(\w+)\s*\(([^)]*)\).*

\s*

(.+)

#', $doc, $matches); + if($numMatches === false) { + throw new RuntimeException("Regexp error: ".preg_last_error()); + } + #var_dump($matches); + foreach($matches[1] as $i=>$name) { + $iniEntries[] = [ + 'name' => strtoupper($name), + 'declaration' => trim(strip_tags($matches[2][$i])), + 'category' => $category, + 'description' => trim(strip_tags($matches[3][$i])), + ]; + } + #break; + } + return finalizeEntries($iniEntries, true); +} + +// SQLite: +# echo gen_sqlite(); + +// MySQL 5.7: +echo gen_mysql(3334); +// MySQL 8.3: +#echo gen_mysql(3308); +// MariaDB 11.7: +# echo gen_mysql(3317); + +// PostgreSQL: +#echo gen_pg(); \ No newline at end of file diff --git a/extra/keywords-generator.php b/extra/keywords-generator.php new file mode 100644 index 000000000..ad68d9fa0 --- /dev/null +++ b/extra/keywords-generator.php @@ -0,0 +1,31 @@ +

ACCESSIBLE + //$htmlList = strtoupper($htmlList); + preg_match_all('#\]*\>\]*\>\]*\>(\w+)\#i', $htmlList, $matches); + //var_dump($matches); + $keywords = []; + foreach ($matches[1] as $kw) { + $kw = strtoupper($kw); + if(!in_array($kw, $funcs)) { + $keywords[] = $kw; + } + } + $keywords = array_unique($keywords); + asort($keywords); + $keywords = implode(' ', $keywords); + $keywords = wordwrap($keywords, 73, "\r\n"); + $keywords = str_replace(' ', ',', $keywords); + $keywords = str_replace("\r\n", ",' +\r\n '", $keywords); + return $keywords; +} + +echo gen_mysql(); \ No newline at end of file diff --git a/out/VC_redist.x64.exe b/out/VC_redist.x64.exe deleted file mode 100644 index ddf6f79fa..000000000 Binary files a/out/VC_redist.x64.exe and /dev/null differ diff --git a/out/VC_redist.x86.exe b/out/VC_redist.x86.exe deleted file mode 100644 index 9c83de901..000000000 Binary files a/out/VC_redist.x86.exe and /dev/null differ diff --git a/out/fbclient-4.0-32.dll b/out/fbclient-4.0-32.dll deleted file mode 100644 index 9b578a346..000000000 Binary files a/out/fbclient-4.0-32.dll and /dev/null differ diff --git a/out/functions-mariadb.ini b/out/functions-mariadb.ini index 5cd6f9079..f01db1b6d 100644 --- a/out/functions-mariadb.ini +++ b/out/functions-mariadb.ini @@ -1,1573 +1,1632 @@ [ABS] declaration=X category=Numeric Functions -description=Returns the absolute (non-negative) value of X. If X is not\na number, it is converted to a numeric type.\n \n\nSELECT ABS(42);\n+---------+\n| ABS(42) |\n+---------+\n| 42 |\n+---------+\n \nSELECT ABS(-42);\n+----------+\n| ABS(-42) |\n+----------+\n| 42 |\n+----------+\n \nSELECT ABS(DATE ''1994-01-01'');\n+------------------------+\n| ABS(DATE ''1994-01-01'') |\n+------------------------+\n| 19940101 |\n+------------------------+ +description=Returns the absolute (non-negative) value of X. If X is not a number, it is\nconverted to a numeric type.\n\nExamples\n--------\n\nSELECT ABS(42);\n+---------+\n| ABS(42) |\n+---------+\n| 42 |\n+---------+\n\nSELECT ABS(-42);\n+----------+\n| ABS(-42) |\n+----------+\n| 42 |\n+----------+\n\nSELECT ABS(DATE '1994-01-01');\n+------------------------+\n| ABS(DATE '1994-01-01') |\n+------------------------+\n| 19940101 |\n+------------------------+\n\nURL: https://mariadb.com/kb/en/abs/ [ACOS] declaration=X category=Numeric Functions -description=Returns the arc cosine of X, that is, the value whose cosine\nis X.\nReturns NULL if X is not in the range -1 to 1.\n \n\nSELECT ACOS(1);\n+---------+\n| ACOS(1) |\n+---------+\n| 0 |\n+---------+\n \nSELECT ACOS(1.0001);\n+--------------+\n| ACOS(1.0001) |\n+--------------+\n| NULL |\n+--------------+\n \nSELECT ACOS(0);\n+-----------------+\n| ACOS(0) |\n+-----------------+\n| 1.5707963267949 |\n+-----------------+\n \nSELECT ACOS(0.234);\n+------------------+\n| ACOS(0.234) |\n+------------------+\n| 1.33460644244679 |\n+------------------+ -[ADDDATE1] -name=ADDDATE +description=Returns the arc cosine of X, that is, the value whose cosine is X. Returns\nNULL if X is not in the range -1 to 1.\n\nExamples\n--------\n\nSELECT ACOS(1);\n+---------+\n| ACOS(1) |\n+---------+\n| 0 |\n+---------+\n\nSELECT ACOS(1.0001);\n+--------------+\n| ACOS(1.0001) |\n+--------------+\n| NULL |\n+--------------+\n\nSELECT ACOS(0);\n+-----------------+\n| ACOS(0) |\n+-----------------+\n| 1.5707963267949 |\n+-----------------+\n\nSELECT ACOS(0.234);\n+------------------+\n| ACOS(0.234) |\n+------------------+\n| 1.33460644244679 |\n+------------------+\n\nURL: https://mariadb.com/kb/en/acos/ +[ADDDATE] declaration=date,INTERVAL expr unit category=Date and Time Functions -description=When invoked with the INTERVAL form of the second argument,\nADDDATE()\nis a synonym for DATE_ADD(). The related function\nSUBDATE() is a synonym for DATE_SUB(). For\ninformation on the INTERVAL unit argument, see the\ndiscussion for\nDATE_ADD().\n \nWhen invoked with the days form of the second argument,\nMariaDB treats it as an\ninteger number of days to be added to expr.\n \n\nSELECT DATE_ADD(''2008-01-02'', INTERVAL 31 DAY);\n+-----------------------------------------+\n| DATE_ADD(''2008-01-02'', INTERVAL 31 DAY) |\n+-----------------------------------------+\n| 2008-02-02 |\n+-----------------------------------------+\n \nSELECT ADDDATE(''2008-01-02'', INTERVAL 31 DAY);\n+----------------------------------------+\n| ADDDATE(''2008-01-02'', INTERVAL 31 DAY) |\n+----------------------------------------+\n| 2008-02-02 |\n+----------------------------------------+\n \nSELECT ADDDATE(''2008-01-02'', 31);\n+---------------------------+\n| ADDDATE(''2008-01-02'', 31) |\n+---------------------------+\n| 2008-02-02 |\n+---------------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT d, ADDDATE(d, 10) from t1;\n \n+---------------------+---------------------+\n| d | ADDDATE(d, 10) |\n+---------------------+---------------------+\n| 2007-01-30 21:31:07 | 2007-02-09 21:31:07 |\n| 1983-10-15 06:42:51 | 1983-10-25 06:42:51 |\n| 2011-04-21 12:34:56 | 2011-05-01 12:34:56 |\n| 2011-10-30 06:31:41 | 2011-11-09 06:31:41 |\n| 2011-01-30 14:03:25 | 2011-02-09 14:03:25 |\n| 2004-10-07 11:19:34 | 2004-10-17 11:19:34 |\n+---------------------+---------------------+\n \nSELECT d, ADDDATE(d, INTERVAL 10 HOUR) from t1;\n \n+---------------------+------------------------------+\n| d | ADDDATE(d, INTERVAL 10 HOUR) |\n+---------------------+------------------------------+\n| 2007-01-30 21:31:07 | 2007-01-31 07:31:07 |\n| 1983-10-15 06:42:51 | 1983-10-15 16:42:51 |\n| 2011-04-21 12:34:56 | 2011-04-21 22:34:56 |\n| 2011-10-30 06:31:41 | 2011-10-30 16:31:41 |\n| 2011-01-30 14:03:25 | 2011-01-31 00:03:25 |\n| 2004-10-07 11:19:34 | 2004-10-07 21:19:34 |\n+---------------------+------------------------------+ -[ADDDATE2] -name=ADDDATE -declaration=expr,days -category=Date and Time Functions -description=When invoked with the INTERVAL form of the second argument,\nADDDATE()\nis a synonym for DATE_ADD(). The related function\nSUBDATE() is a synonym for DATE_SUB(). For\ninformation on the INTERVAL unit argument, see the\ndiscussion for\nDATE_ADD().\n \nWhen invoked with the days form of the second argument,\nMariaDB treats it as an\ninteger number of days to be added to expr.\n \n\nSELECT DATE_ADD(''2008-01-02'', INTERVAL 31 DAY);\n+-----------------------------------------+\n| DATE_ADD(''2008-01-02'', INTERVAL 31 DAY) |\n+-----------------------------------------+\n| 2008-02-02 |\n+-----------------------------------------+\n \nSELECT ADDDATE(''2008-01-02'', INTERVAL 31 DAY);\n+----------------------------------------+\n| ADDDATE(''2008-01-02'', INTERVAL 31 DAY) |\n+----------------------------------------+\n| 2008-02-02 |\n+----------------------------------------+\n \nSELECT ADDDATE(''2008-01-02'', 31);\n+---------------------------+\n| ADDDATE(''2008-01-02'', 31) |\n+---------------------------+\n| 2008-02-02 |\n+---------------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT d, ADDDATE(d, 10) from t1;\n \n+---------------------+---------------------+\n| d | ADDDATE(d, 10) |\n+---------------------+---------------------+\n| 2007-01-30 21:31:07 | 2007-02-09 21:31:07 |\n| 1983-10-15 06:42:51 | 1983-10-25 06:42:51 |\n| 2011-04-21 12:34:56 | 2011-05-01 12:34:56 |\n| 2011-10-30 06:31:41 | 2011-11-09 06:31:41 |\n| 2011-01-30 14:03:25 | 2011-02-09 14:03:25 |\n| 2004-10-07 11:19:34 | 2004-10-17 11:19:34 |\n+---------------------+---------------------+\n \nSELECT d, ADDDATE(d, INTERVAL 10 HOUR) from t1;\n \n+---------------------+------------------------------+\n| d | ADDDATE(d, INTERVAL 10 HOUR) |\n+---------------------+------------------------------+\n| 2007-01-30 21:31:07 | 2007-01-31 07:31:07 |\n| 1983-10-15 06:42:51 | 1983-10-15 16:42:51 |\n| 2011-04-21 12:34:56 | 2011-04-21 22:34:56 |\n| 2011-10-30 06:31:41 | 2011-10-30 16:31:41 |\n| 2011-01-30 14:03:25 | 2011-01-31 00:03:25 |\n| 2004-10-07 11:19:34 | 2004-10-07 21:19:34 |\n+---------------------+------------------------------+ +description=When invoked with the INTERVAL form of the second argument, ADDDATE() is a\nsynonym for DATE_ADD(). The related function SUBDATE() is a synonym for\nDATE_SUB(). For information on the INTERVAL unit argument, see the discussion\nfor DATE_ADD().\n\nWhen invoked with the days form of the second argument, MariaDB treats it as\nan integer number of days to be added to expr.\n\nExamples\n--------\n\nSELECT DATE_ADD('2008-01-02', INTERVAL 31 DAY);\n+-----------------------------------------+\n| DATE_ADD('2008-01-02', INTERVAL 31 DAY) |\n+-----------------------------------------+\n| 2008-02-02 |\n+-----------------------------------------+\n\nSELECT ADDDATE('2008-01-02', INTERVAL 31 DAY);\n+----------------------------------------+\n| ADDDATE('2008-01-02', INTERVAL 31 DAY) |\n+----------------------------------------+\n| 2008-02-02 |\n+----------------------------------------+\n\nSELECT ADDDATE('2008-01-02', 31);\n+---------------------------+\n| ADDDATE('2008-01-02', 31) |\n+---------------------------+\n| 2008-02-02 |\n+---------------------------+\n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n\nSELECT d, ADDDATE(d, 10) from t1;\n+---------------------+---------------------+\n| d | ADDDATE(d, 10) |\n+---------------------+---------------------+\n| 2007-01-30 21:31:07 | 2007-02-09 21:31:07 |\n| 1983-10-15 06:42:51 | 1983-10-25 06:42:51 |\n| 2011-04-21 12:34:56 | 2011-05-01 12:34:56 |\n| 2011-10-30 06:31:41 | 2011-11-09 06:31:41 |\n| 2011-01-30 14:03:25 | 2011-02-09 14:03:25 |\n ... [ADDTIME] declaration=expr1,expr2 category=Date and Time Functions -description=ADDTIME() adds expr2 to expr1 and returns the result. expr1\nis a time\nor datetime expression, and expr2 is a time expression.\n \n\nSELECT ADDTIME(''2007-12-31 23:59:59.999999'', ''1\n1:1:1.000002'');\n+---------------------------------------------------------+\n| ADDTIME(''2007-12-31 23:59:59.999999'', ''1\n1:1:1.000002'') |\n+---------------------------------------------------------+\n| 2008-01-02 01:01:01.000001 |\n+---------------------------------------------------------+\n \nSELECT ADDTIME(''01:00:00.999999'', ''02:00:00.999998'');\n+-----------------------------------------------+\n| ADDTIME(''01:00:00.999999'', ''02:00:00.999998'') |\n+-----------------------------------------------+\n| 03:00:01.999997 |\n+-----------------------------------------------+ +description=ADDTIME() adds expr2 to expr1 and returns the result. expr1 is a time or\ndatetime expression, and expr2 is a time expression.\n\nExamples\n--------\n\nSELECT ADDTIME('2007-12-31 23:59:59.999999', '1 1:1:1.000002');\n+---------------------------------------------------------+\n| ADDTIME('2007-12-31 23:59:59.999999', '1 1:1:1.000002') |\n+---------------------------------------------------------+\n| 2008-01-02 01:01:01.000001 |\n+---------------------------------------------------------+\n\nSELECT ADDTIME('01:00:00.999999', '02:00:00.999998');\n+-----------------------------------------------+\n| ADDTIME('01:00:00.999999', '02:00:00.999998') |\n+-----------------------------------------------+\n| 03:00:01.999997 |\n+-----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/addtime/ +[ADD_MONTHS] +declaration=date, months +category=Date and Time Functions +description=ADD_MONTHS adds an integer months to a given date (DATE, DATETIME or\nTIMESTAMP), returning the resulting date.\n\nmonths can be positive or negative. If months is not a whole number, then it\nwill be rounded to the nearest whole number (not truncated).\n\nThe resulting day component will remain the same as that specified in date,\nunless the resulting month has fewer days than the day component of the given\ndate, in which case the day will be the last day of the resulting month.\n\nReturns NULL if given an invalid date, or a NULL argument.\n\nExamples\n--------\n\nSELECT ADD_MONTHS('2012-01-31', 2);\n+-----------------------------+\n| ADD_MONTHS('2012-01-31', 2) |\n+-----------------------------+\n| 2012-03-31 |\n+-----------------------------+\n\nSELECT ADD_MONTHS('2012-01-31', -5);\n+------------------------------+\n| ADD_MONTHS('2012-01-31', -5) |\n+------------------------------+\n| 2011-08-31 |\n+------------------------------+\n\nSELECT ADD_MONTHS('2011-01-31', 1);\n+-----------------------------+\n| ADD_MONTHS('2011-01-31', 1) |\n+-----------------------------+\n| 2011-02-28 |\n+-----------------------------+\n\nSELECT ADD_MONTHS('2012-01-31', 1);\n+-----------------------------+\n| ADD_MONTHS('2012-01-31', 1) |\n+-----------------------------+\n| 2012-02-29 |\n+-----------------------------+\n\nSELECT ADD_MONTHS('2012-01-31', 2);\n+-----------------------------+\n| ADD_MONTHS('2012-01-31', 2) |\n+-----------------------------+\n| 2012-03-31 |\n+-----------------------------+\n\n ... [AES_DECRYPT] declaration=crypt_str,key_str category=Encryption Functions -description=This function allows decryption of data using the official\nAES\n(Advanced Encryption Standard) algorithm. For more\ninformation, see\nthe description of AES_ENCRYPT(). +description=This function allows decryption of data using the official AES (Advanced\nEncryption Standard) algorithm. For more information, see the description of\nAES_ENCRYPT().\n\nMariaDB starting with 11.2\n--------------------------\nFrom MariaDB 11.2, the function supports an initialization vector, and control\nof the block encryption mode. The default mode is specified by the\nblock_encryption_mode system variable, which can be changed when calling the\nfunction with a mode. mode is aes-{128,192,256}-{ecb,cbc,ctr} for example:\n"AES-128-cbc".\n\nFor modes that require it, the initialization_vector iv should be 16 bytes (it\ncan be longer, but the extra bytes are ignored). A shorter iv, where one is\nrequired, results in the function returning NULL. Calling RANDOM_BYTES(16)\nwill generate a random series of bytes that can be used for the iv.\n\nExamples\n--------\n\nFrom MariaDB 11.2.0:\n\nSELECT HEX(AES_ENCRYPT('foo', 'bar', '0123456789abcdef', 'aes-128-ctr')) AS x; \n+--------+\n| x |\n+--------+\n| C57C4B |\n+--------+\n\nSELECT AES_DECRYPT(x'C57C4B', 'bar', '0123456789abcdef', 'aes-128-ctr'); \n+------------------------------------------------------------------+\n| AES_DECRYPT(x'C57C4B', 'bar', '0123456789abcdef', 'aes-128-ctr') |\n+------------------------------------------------------------------+\n| foo |\n+------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/aes_decrypt/ [AES_ENCRYPT] declaration=str,key_str category=Encryption Functions -description=AES_ENCRYPT() and AES_DECRYPT() allow encryption and\ndecryption of\ndata using the official AES (Advanced Encryption Standard)\nalgorithm,\npreviously known as "Rijndael." Encoding with a 128-bit\nkey length is\nused, but you can extend it up to 256 bits by modifying the\nsource. We\nchose 128 bits because it is much faster and it is secure\nenough for\nmost purposes.\n \nAES_ENCRYPT() encrypts a string str using the key key_str,\nand returns a binary string.\n \nAES_DECRYPT() decrypts the encrypted string and returns the\noriginal\nstring.\n \nThe input arguments may be any length. If either argument is\nNULL, the result of this function is also NULL.\n \nBecause AES is a block-level algorithm, padding is used to\nencode\nuneven length strings and so the result string length may be\ncalculated using this formula:\n \n16 x (trunc(string_length / 16) + 1)\n \nIf AES_DECRYPT() detects invalid data or incorrect padding,\nit returns\nNULL. However, it is possible for AES_DECRYPT() to return a\nnon-NULL\nvalue (possibly garbage) if the input data or the key is\ninvalid.\n \n\nINSERT INTO t VALUES\n(AES_ENCRYPT(''text'',SHA2(''password'',512))); +description=AES_ENCRYPT() and AES_DECRYPT() allow encryption and decryption of data using\nthe official AES (Advanced Encryption Standard) algorithm, previously known as\n"Rijndael." Encoding with a 128-bit key length is used (from MariaDB 11.2.0,\nthis is the default, and can be changed). 128 bits is much faster and is\nsecure enough for most purposes.\n\nAES_ENCRYPT() encrypts a string str using the key key_str, and returns a\nbinary string.\n\nAES_DECRYPT() decrypts the encrypted string and returns the original string.\n\nThe input arguments may be any length. If either argument is NULL, the result\nof this function is also NULL.\n\nBecause AES is a block-level algorithm, padding is used to encode uneven\nlength strings and so the result string length may be calculated using this\nformula:\n\n16 x (trunc(string_length / 16) + 1)\n\nIf AES_DECRYPT() detects invalid data or incorrect padding, it returns NULL.\nHowever, it is possible for AES_DECRYPT() to return a non-NULL value (possibly\ngarbage) if the input data or the key is invalid.\n\nMariaDB starting with 11.2\n--------------------------\nFrom MariaDB 11.2, the function supports an initialization vector, and control\nof the block encryption mode. The default mode is specified by the\nblock_encryption_mode system variable, which can be changed when calling the\nfunction with a mode. mode is aes-{128,192,256}-{ecb,cbc,ctr} for example:\n"AES-128-cbc".\n\nAES_ENCRYPT(str, key) can no longer be used in persistent virtual columns (and\nthe like).\n\nExamples\n--------\n\nINSERT INTO t VALUES (AES_ENCRYPT('text',SHA2('password',512)));\n\nFrom MariaDB 11.2.0:\n\nSELECT HEX(AES_ENCRYPT('foo', 'bar', '0123456789abcdef', 'aes-256-cbc')) AS x;\n+----------------------------------+\n| x |\n+----------------------------------+\n| 42A3EB91E6DFC40A900D278F99E0726E |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/aes_encrypt/ [ASCII] declaration=str category=String Functions -description=Returns the numeric ASCII value of the leftmost character of\nthe string argument. Returns 0 if the given string is empty\nand NULL if it is NULL.\n \nASCII() works for 8-bit characters.\n \n\nSELECT ASCII(9);\n+----------+\n| ASCII(9) |\n+----------+\n| 57 |\n+----------+\n \nSELECT ASCII(''9'');\n+------------+\n| ASCII(''9'') |\n+------------+\n| 57 |\n+------------+\n \nSELECT ASCII(''abc'');\n+--------------+\n| ASCII(''abc'') |\n+--------------+\n| 97 |\n+--------------+ +description=Returns the numeric ASCII value of the leftmost character of the string\nargument. Returns 0 if the given string is empty and NULL if it is NULL.\n\nASCII() works for 8-bit characters.\n\nExamples\n--------\n\nSELECT ASCII(9);\n+----------+\n| ASCII(9) |\n+----------+\n| 57 |\n+----------+\n\nSELECT ASCII('9');\n+------------+\n| ASCII('9') |\n+------------+\n| 57 |\n+------------+\n\nSELECT ASCII('abc');\n+--------------+\n| ASCII('abc') |\n+--------------+\n| 97 |\n+--------------+\n\nURL: https://mariadb.com/kb/en/ascii/ [ASIN] declaration=X category=Numeric Functions -description=Returns the arc sine of X, that is, the value whose sine is\nX. Returns\nNULL if X is not in the range -1 to 1.\n \n\nSELECT ASIN(0.2);\n+--------------------+\n| ASIN(0.2) |\n+--------------------+\n| 0.2013579207903308 |\n+--------------------+\n \nSELECT ASIN(''foo'');\n+-------------+\n| ASIN(''foo'') |\n+-------------+\n| 0 |\n+-------------+\n \nSHOW WARNINGS;\n \n+---------+------+-----------------------------------------+\n| Level | Code | Message |\n+---------+------+-----------------------------------------+\n| Warning | 1292 | Truncated incorrect DOUBLE value: ''foo''\n|\n+---------+------+-----------------------------------------+ +description=Returns the arc sine of X, that is, the value whose sine is X. Returns NULL if\nX is not in the range -1 to 1.\n\nExamples\n--------\n\nSELECT ASIN(0.2);\n+--------------------+\n| ASIN(0.2) |\n+--------------------+\n| 0.2013579207903308 |\n+--------------------+\n\nSELECT ASIN('foo');\n+-------------+\n| ASIN('foo') |\n+-------------+\n| 0 |\n+-------------+\n\nSHOW WARNINGS;\n+---------+------+-----------------------------------------+\n| Level | Code | Message |\n+---------+------+-----------------------------------------+\n| Warning | 1292 | Truncated incorrect DOUBLE value: 'foo' |\n+---------+------+-----------------------------------------+\n\nURL: https://mariadb.com/kb/en/asin/ [ATAN] declaration=X category=Numeric Functions -description=Returns the arc tangent of X, that is, the value whose\ntangent is X.\n \n\nSELECT ATAN(2);\n+--------------------+\n| ATAN(2) |\n+--------------------+\n| 1.1071487177940904 |\n+--------------------+\n \nSELECT ATAN(-2);\n+---------------------+\n| ATAN(-2) |\n+---------------------+\n| -1.1071487177940904 |\n+---------------------+ +description=Returns the arc tangent of X, that is, the value whose tangent is X.\n\nExamples\n--------\n\nSELECT ATAN(2);\n+--------------------+\n| ATAN(2) |\n+--------------------+\n| 1.1071487177940904 |\n+--------------------+\n\nSELECT ATAN(-2);\n+---------------------+\n| ATAN(-2) |\n+---------------------+\n| -1.1071487177940904 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/atan/ +[ATAN2] +declaration=Y,X +category=Numeric Functions +description=Returns the arc tangent of the two variables X and Y. It is similar to\ncalculating the arc tangent of Y / X, except that the signs of both arguments\nare used to determine the quadrant of the result.\n\nExamples\n--------\n\nSELECT ATAN(-2,2);\n+---------------------+\n| ATAN(-2,2) |\n+---------------------+\n| -0.7853981633974483 |\n+---------------------+\n\nSELECT ATAN2(PI(),0);\n+--------------------+\n| ATAN2(PI(),0) |\n+--------------------+\n| 1.5707963267948966 |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/atan2/ [AVG] declaration=[DISTINCT] expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the average value of expr. The DISTINCT option can\nbe used to return the average of the distinct values of\nexpr. NULL values are ignored. It is an aggregate function,\nand so can be used with the GROUP BY clause.\n \nAVG() returns NULL if there were no matching rows.\n \nFrom MariaDB 10.2.0, AVG() can be used as a window function.\n \n\nCREATE TABLE sales (sales_value INT);\n \nINSERT INTO sales VALUES(10),(20),(20),(40);\n \nSELECT AVG(sales_value) FROM sales;\n \n+------------------+\n| AVG(sales_value) |\n+------------------+\n| 22.5000 |\n+------------------+\n \nSELECT AVG(DISTINCT(sales_value)) FROM sales;\n \n+----------------------------+\n| AVG(DISTINCT(sales_value)) |\n+----------------------------+\n| 23.3333 |\n+----------------------------+\n \nCommonly, AVG() is used with a GROUP BY clause:\n \nCREATE TABLE student (name CHAR(10), test CHAR(10), score\nTINYINT); \n \nINSERT INTO student VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87), (''Tatiana'', ''Tuning'', 83);\n \nSELECT name, AVG(score) FROM student GROUP BY name;\n \n+---------+------------+\n| name | AVG(score) |\n+---------+------------+\n| Chun | 74.0000 |\n| Esben | 37.0000 |\n| Kaolin | 72.0000 |\n| Tatiana | 85.0000 |\n+---------+------------+\n \nBe careful to avoid this common mistake, not grouping\ncorrectly and returning mismatched data: \n \nSELECT name,test,AVG(score) FROM student;\n \n+------+------+------------+\n| name | test | MIN(score) |\n+------+------+------------+\n| Chun | SQL | 31 |\n+------+------+------------+\n \nAs a window function:\n \nCREATE TABLE student_test (name CHAR(10), test CHAR(10),\nscore TINYINT); \n \nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87), (''Tatiana'', ''Tuning'', 83);\n \nSELECT name, test, score, AVG(score) OVER (PARTITION BY\ntest) \n AS average_by_test FROM student_test;\n \n+---------+--------+-------+-----------------+\n| name | test | score | average_by_test |\n+---------+--------+-------+-----------------+\n| Chun | SQL | 75 | 65.2500 |\n| Chun | Tuning | 73 | 68.7500 |\n| Esben | SQL | 43 | 65.2500 |\n| Esben | Tuning | 31 | 68.7500 |\n| Kaolin | SQL | 56 | 65.2500 |\n| Kaolin | Tuning | 88 | 68.7500 |\n| Tatiana | SQL | 87 | 65.2500 |\n| Tatiana | Tuning | 83 | 68.7500 |\n+---------+--------+-------+-----------------+ +description=Returns the average value of expr. The DISTINCT option can be used to return\nthe average of the distinct values of expr. NULL values are ignored. It is an\naggregate function, and so can be used with the GROUP BY clause.\n\nAVG() returns NULL if there were no matching rows.\n\nAVG() can be used as a window function.\n\nExamples\n--------\n\nCREATE TABLE sales (sales_value INT);\n\nINSERT INTO sales VALUES(10),(20),(20),(40);\n\nSELECT AVG(sales_value) FROM sales;\n+------------------+\n| AVG(sales_value) |\n+------------------+\n| 22.5000 |\n+------------------+\n\nSELECT AVG(DISTINCT(sales_value)) FROM sales;\n+----------------------------+\n| AVG(DISTINCT(sales_value)) |\n+----------------------------+\n| 23.3333 |\n+----------------------------+\n\nCommonly, AVG() is used with a GROUP BY clause:\n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT);\n\nINSERT INTO student VALUES \n ('Chun', 'SQL', 75), ('Chun', 'Tuning', 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87), ('Tatiana', 'Tuning', 83);\n\nSELECT name, AVG(score) FROM student GROUP BY name;\n+---------+------------+\n| name | AVG(score) |\n+---------+------------+\n| Chun | 74.0000 |\n| Esben | 37.0000 |\n| Kaolin | 72.0000 |\n| Tatiana | 85.0000 |\n+---------+------------+\n\nBe careful to avoid this common mistake, not grouping correctly and returning\n ... [BENCHMARK] declaration=count,expr category=Information Functions -description=The BENCHMARK() function executes the expression expr\nrepeatedly count\ntimes. It may be used to time how quickly MariaDB processes\nthe\nexpression. The result value is always 0. The intended use\nis from\nwithin the mysql client, which reports query execution\ntimes.\n \n\nSELECT BENCHMARK(1000000,ENCODE(''hello'',''goodbye''));\n+----------------------------------------------+\n| BENCHMARK(1000000,ENCODE(''hello'',''goodbye'')) |\n+----------------------------------------------+\n| 0 |\n+----------------------------------------------+\n1 row in set (0.21 sec) +description=The BENCHMARK() function executes the expression expr repeatedly count times.\nIt may be used to time how quickly MariaDB processes the expression. The\nresult value is always 0. The intended use is from within the mariadb client,\nwhich reports query execution times.\n\nExamples\n--------\n\nSELECT BENCHMARK(1000000,ENCODE('hello','goodbye'));\n+----------------------------------------------+\n| BENCHMARK(1000000,ENCODE('hello','goodbye')) |\n+----------------------------------------------+\n| 0 |\n+----------------------------------------------+\n1 row in set (0.21 sec)\n\nURL: https://mariadb.com/kb/en/benchmark/ +[BIGINT] +declaration=M +category=Data Types +description=A large integer. The signed range is -9223372036854775808 to\n9223372036854775807. The unsigned range is 0 to 18446744073709551615.\n\nIf a column has been set to ZEROFILL, all values will be prepended by zeros so\nthat the BIGINT value contains a number of M digits.\n\nNote: If the ZEROFILL attribute has been specified, the column will\nautomatically become UNSIGNED.\n\nFor more details on the attributes, see Numeric Data Type Overview.\n\nSERIAL is an alias for:\n\nBIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE\n\nINT8 is a synonym for BIGINT.\n\nExamples\n--------\n\nCREATE TABLE bigints (a BIGINT,b BIGINT UNSIGNED,c BIGINT ZEROFILL);\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO bigints VALUES (-10,-10,-10);\nERROR 1264 (22003): Out of range value for column 'b' at row 1\n\nINSERT INTO bigints VALUES (-10,10,-10);\nERROR 1264 (22003): Out of range value for column 'c' at row 1\n\nINSERT INTO bigints VALUES (-10,10,10);\n\nINSERT INTO bigints VALUES\n(9223372036854775808,9223372036854775808,9223372036854775808);\nERROR 1264 (22003): Out of range value for column 'a' at row 1\n\nINSERT INTO bigints VALUES\n(9223372036854775807,9223372036854775808,9223372036854775808);\n\nSELECT * FROM bigints;\n+---------------------+---------------------+----------------------+\n| a | b | c |\n+---------------------+---------------------+----------------------+\n| -10 | 10 | 00000000000000000010 |\n| 9223372036854775807 | 9223372036854775808 | 09223372036854775808 |\n+---------------------+---------------------+----------------------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO bigints VALUES (-10,-10,-10);\n ... +[BIN] +declaration=N +category=String Functions +description=Returns a string representation of the binary value of the given longlong\n(that is, BIGINT) number. This is equivalent to CONV(N,10,2). The argument\nshould be positive. If it is a FLOAT, it will be truncated. Returns NULL if\nthe argument is NULL.\n\nExamples\n--------\n\nSELECT BIN(12);\n+---------+\n| BIN(12) |\n+---------+\n| 1100 |\n+---------+\n\nURL: https://mariadb.com/kb/en/bin/ [BINARY] declaration=M category=Data Types -description=The BINARY type is similar to the CHAR type, but stores\nbinary\nbyte strings rather than non-binary character strings. M\nrepresents the\ncolumn length in bytes.\n \nIt contains no character set, and comparison and sorting are\nbased on the numeric value of the bytes.\n \nIf the maximum length is exceeded, and SQL strict mode is\nnot enabled , the extra characters will be dropped with a\nwarning. If strict mode is enabled, an error will occur.\n \nBINARY values are right-padded with 0x00 (the zero byte) to\nthe specified length when inserted. The padding is not\nremoved on select, so this needs to be taken into account\nwhen sorting and comparing, where all bytes are significant.\nThe zero byte, 0x00 is less than a space for comparison\npurposes.\n \n\nInserting too many characters, first with strict mode off,\nthen with it on:\n \nCREATE TABLE bins (a BINARY(10));\n \nINSERT INTO bins VALUES(''12345678901'');\nQuery OK, 1 row affected, 1 warning (0.04 sec)\n \nSELECT * FROM bins;\n \n+------------+\n| a |\n+------------+\n| 1234567890 |\n+------------+\n \nSET sql_mode=''STRICT_ALL_TABLES'';\n \nINSERT INTO bins VALUES(''12345678901'');\nERROR 1406 (22001): Data too long for column ''a'' at row 1\n \nSorting is performed with the byte value:\n \nTRUNCATE bins;\n \nINSERT INTO bins VALUES(''A''),(''B''),(''a''),(''b'');\n \nSELECT * FROM bins ORDER BY a;\n \n+------+\n| a |\n+------+\n| A |\n| B |\n| a |\n| b |\n+------+\n \nUsing CAST to sort as a CHAR instead:\n \nSELECT * FROM bins ORDER BY CAST(a AS CHAR);\n+------+\n| a |\n+------+\n| a |\n| A |\n| b |\n| B |\n+------+\n \nThe field is a BINARY(10), so padding of two ''\0''s are\ninserted, causing comparisons that don''t take this into\naccount to fail:\n \nTRUNCATE bins;\n \nINSERT INTO bins VALUES(''12345678'');\n \nSELECT a = ''12345678'', a = ''12345678\0\0'' from bins;\n \n+----------------+--------------------+\n| a = ''12345678'' | a = ''12345678\0\0'' |\n+----------------+--------------------+\n| 0 | 1 |\n+----------------+--------------------+ +description=The BINARY type is similar to the CHAR type, but stores binary byte strings\nrather than non-binary character strings. M represents the column length in\nbytes.\n\nIt contains no character set, and comparison and sorting are based on the\nnumeric value of the bytes.\n\nIf the maximum length is exceeded, and SQL strict mode is not enabled , the\nextra characters will be dropped with a warning. If strict mode is enabled, an\nerror will occur.\n\nBINARY values are right-padded with 0x00 (the zero byte) to the specified\nlength when inserted. The padding is not removed on select, so this needs to\nbe taken into account when sorting and comparing, where all bytes are\nsignificant. The zero byte, 0x00 is less than a space for comparison purposes.\n\nExamples\n--------\n\nInserting too many characters, first with strict mode off, then with it on:\n\nCREATE TABLE bins (a BINARY(10));\n\nINSERT INTO bins VALUES('12345678901');\nQuery OK, 1 row affected, 1 warning (0.04 sec)\n\nSELECT * FROM bins;\n+------------+\n| a |\n+------------+\n| 1234567890 |\n+------------+\n\nSET sql_mode='STRICT_ALL_TABLES';\n\nINSERT INTO bins VALUES('12345678901');\nERROR 1406 (22001): Data too long for column 'a' at row 1\n\nSorting is performed with the byte value:\n\nTRUNCATE bins;\n\nINSERT INTO bins VALUES('A'),('B'),('a'),('b');\n\nSELECT * FROM bins ORDER BY a;\n+------+\n| a |\n+------+\n| A |\n| B |\n ... [BINLOG_GTID_POS] declaration=binlog_filename,binlog_offset category=Information Functions -description=The BINLOG_GTID_POS() function takes as input an old-style\nbinary log position in the form of a file name and a file\noffset. It looks up the position in the current binlog, and\nreturns a string representation of the corresponding GTID\nposition. If the position is not found in the current\nbinlog, NULL is returned.\n \n\nSELECT BINLOG_GTID_POS("master-bin.000001", 600); -[BIN] -declaration=N -category=String Functions -description=Returns a string representation of the binary value of the\ngiven longlong (that is, BIGINT) number. This is equivalent\nto CONV(N,10,2). The argument should be positive. If it is a\nFLOAT, it will be truncated. Returns NULL if the argument is\nNULL.\n \n\nSELECT BIN(12);\n+---------+\n| BIN(12) |\n+---------+\n| 1100 |\n+---------+ +description=The BINLOG_GTID_POS() function takes as input an old-style binary log position\nin the form of a file name and a file offset. It looks up the position in the\ncurrent binlog, and returns a string representation of the corresponding GTID\nposition. If the position is not found in the current binlog, NULL is returned.\n\nExamples\n--------\n\nSELECT BINLOG_GTID_POS("master-bin.000001", 600);\n\nURL: https://mariadb.com/kb/en/binlog_gtid_pos/ +[BIT] +declaration=M +category=Data Types +description=A bit-field type. M indicates the number of bits per value, from 1 to 64. The\ndefault is 1 if M is omitted.\n\nBit values can be inserted with b'value' notation, where value is the bit\nvalue in 0's and 1's.\n\nBit fields are automatically zero-padded from the left to the full length of\nthe bit, so for example in a BIT(4) field, '10' is equivalent to '0010'.\n\nBits are returned as binary, so to display them, either add 0, or use a\nfunction such as HEX, OCT or BIN to convert them.\n\nExamples\n--------\n\nCREATE TABLE b ( b1 BIT(8) );\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO b VALUES (b'11111111');\n\nINSERT INTO b VALUES (b'01010101');\n\nINSERT INTO b VALUES (b'1111111111111');\nERROR 1406 (22001): Data too long for column 'b1' at row 1\n\nSELECT b1+0, HEX(b1), OCT(b1), BIN(b1) FROM b;\n+------+---------+---------+----------+\n| b1+0 | HEX(b1) | OCT(b1) | BIN(b1) |\n+------+---------+---------+----------+\n| 255 | FF | 377 | 11111111 |\n| 85 | 55 | 125 | 1010101 |\n+------+---------+---------+----------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO b VALUES (b'11111111'),(b'01010101'),(b'1111111111111');\nQuery OK, 3 rows affected, 1 warning (0.10 sec)\nRecords: 3 Duplicates: 0 Warnings: 1\n\nSHOW WARNINGS;\n+---------+------+---------------------------------------------+\n| Level | Code | Message |\n+---------+------+---------------------------------------------+\n| Warning | 1264 | Out of range value for column 'b1' at row 3 |\n+---------+------+---------------------------------------------+\n\nSELECT b1+0, HEX(b1), OCT(b1), BIN(b1) FROM b;\n+------+---------+---------+----------+\n| b1+0 | HEX(b1) | OCT(b1) | BIN(b1) |\n ... [BIT_AND] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the bitwise AND of all bits in expr. The calculation\nis performed with 64-bit (BIGINT) precision. It is an\naggregate function, and so can be used with the GROUP BY\nclause.\n \nFrom MariaDB 10.2.0, BIT_AND() can be used as a window\nfunction.\n \n\nCREATE TABLE vals (x INT);\n \nINSERT INTO vals VALUES(111),(110),(100);\n \nSELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals;\n \n+------------+-----------+------------+\n| BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+------------+-----------+------------+\n| 100 | 111 | 101 |\n+------------+-----------+------------+\n \nAs an aggregate function:\n \nCREATE TABLE vals2 (category VARCHAR(1), x INT);\n \nINSERT INTO vals2 VALUES\n (''a'',111),(''a'',110),(''a'',100),\n (''b'',''000''),(''b'',001),(''b'',011);\n \nSELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) \n FROM vals GROUP BY category;\n \n+----------+------------+-----------+------------+\n| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+----------+------------+-----------+------------+\n| a | 100 | 111 | 101 |\n| b | 0 | 11 | 10 |\n+----------+------------+-----------+------------+ +description=Returns the bitwise AND of all bits in expr. The calculation is performed with\n64-bit (BIGINT) precision. It is an aggregate function, and so can be used\nwith the GROUP BY clause.\n\nIf no rows match, BIT_AND will return a value with all bits set to 1. NULL\nvalues have no effect on the result unless all results are NULL, which is\ntreated as no match.\n\nBIT_AND can be used as a window function with the addition of the over_clause.\n\nExamples\n--------\n\nCREATE TABLE vals (x INT);\n\nINSERT INTO vals VALUES(111),(110),(100);\n\nSELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals;\n+------------+-----------+------------+\n| BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+------------+-----------+------------+\n| 100 | 111 | 101 |\n+------------+-----------+------------+\n\nAs an aggregate function:\n\nCREATE TABLE vals2 (category VARCHAR(1), x INT);\n\nINSERT INTO vals2 VALUES\n ('a',111),('a',110),('a',100),\n ('b','000'),('b',001),('b',011);\n\nSELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) \n FROM vals GROUP BY category;\n+----------+------------+-----------+------------+\n| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+----------+------------+-----------+------------+\n| a | 100 | 111 | 101 |\n| b | 0 | 11 | 10 |\n+----------+------------+-----------+------------+\n\nNo match:\n\nSELECT BIT_AND(NULL);\n+----------------------+\n| BIT_AND(NULL) |\n+----------------------+\n| 18446744073709551615 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/bit_and/ [BIT_COUNT] declaration=N category=Bit Functions -description=Returns the number of bits that are set in the argument N.\n \n\nSELECT BIT_COUNT(29), BIT_COUNT(b''101010'');\n+---------------+----------------------+\n| BIT_COUNT(29) | BIT_COUNT(b''101010'') |\n+---------------+----------------------+\n| 4 | 3 |\n+---------------+----------------------+ +description=Returns the number of bits that are set in the argument N.\n\nExamples\n--------\n\nSELECT BIT_COUNT(29), BIT_COUNT(b'101010');\n+---------------+----------------------+\n| BIT_COUNT(29) | BIT_COUNT(b'101010') |\n+---------------+----------------------+\n| 4 | 3 |\n+---------------+----------------------+\n\nURL: https://mariadb.com/kb/en/bit_count/ [BIT_LENGTH] declaration=str category=String Functions -description=Returns the length of the given string argument in bits. If\nthe argument is not a string, it will be converted to\nstring. If the argument is NULL, it returns NULL.\n \n\nSELECT BIT_LENGTH(''text'');\n+--------------------+\n| BIT_LENGTH(''text'') |\n+--------------------+\n| 32 |\n+--------------------+\n \nSELECT BIT_LENGTH('''');\n+----------------+\n| BIT_LENGTH('''') |\n+----------------+\n| 0 |\n+----------------+\n \nCompatibility\n \nPostgreSQL and Sybase support BIT_LENGTH(). +description=Returns the length of the given string argument in bits. If the argument is\nnot a string, it will be converted to string. If the argument is NULL, it\nreturns NULL.\n\nExamples\n--------\n\nSELECT BIT_LENGTH('text');\n+--------------------+\n| BIT_LENGTH('text') |\n+--------------------+\n| 32 |\n+--------------------+\n\nSELECT BIT_LENGTH('');\n+----------------+\n| BIT_LENGTH('') |\n+----------------+\n| 0 |\n+----------------+\n\nCompatibility\n-------------\n\nPostgreSQL and Sybase support BIT_LENGTH().\n\nURL: https://mariadb.com/kb/en/bit_length/ [BIT_OR] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the bitwise OR of all bits in expr. The calculation\nis performed with 64-bit (BIGINT) precision. It is an\naggregate function, and so can be used with the GROUP BY\nclause.\n \nFrom MariaDB 10.2.0, BIT_OR can be used as a window\nfunction.\n \n\nCREATE TABLE vals (x INT);\n \nINSERT INTO vals VALUES(111),(110),(100);\n \nSELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals;\n \n+------------+-----------+------------+\n| BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+------------+-----------+------------+\n| 100 | 111 | 101 |\n+------------+-----------+------------+\n \nAs an aggregate function:\n \nCREATE TABLE vals2 (category VARCHAR(1), x INT);\n \nINSERT INTO vals2 VALUES\n (''a'',111),(''a'',110),(''a'',100),\n (''b'',''000''),(''b'',001),(''b'',011);\n \nSELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) \n FROM vals GROUP BY category;\n \n+----------+------------+-----------+------------+\n| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+----------+------------+-----------+------------+\n| a | 100 | 111 | 101 |\n| b | 0 | 11 | 10 |\n+----------+------------+-----------+------------+ +description=Returns the bitwise OR of all bits in expr. The calculation is performed with\n64-bit (BIGINT) precision. It is an aggregate function, and so can be used\nwith the GROUP BY clause.\n\nIf no rows match, BIT_OR will return a value with all bits set to 0. NULL\nvalues have no effect on the result unless all results are NULL, which is\ntreated as no match.\n\nBIT_OR can be used as a window function with the addition of the over_clause.\n\nExamples\n--------\n\nCREATE TABLE vals (x INT);\n\nINSERT INTO vals VALUES(111),(110),(100);\n\nSELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals;\n+------------+-----------+------------+\n| BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+------------+-----------+------------+\n| 100 | 111 | 101 |\n+------------+-----------+------------+\n\nAs an aggregate function:\n\nCREATE TABLE vals2 (category VARCHAR(1), x INT);\n\nINSERT INTO vals2 VALUES\n ('a',111),('a',110),('a',100),\n ('b','000'),('b',001),('b',011);\n\nSELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) \n FROM vals GROUP BY category;\n+----------+------------+-----------+------------+\n| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+----------+------------+-----------+------------+\n| a | 100 | 111 | 101 |\n| b | 0 | 11 | 10 |\n+----------+------------+-----------+------------+\n\nNo match:\n\nSELECT BIT_OR(NULL);\n+--------------+\n| BIT_OR(NULL) |\n+--------------+\n| 0 |\n+--------------+\n\nURL: https://mariadb.com/kb/en/bit_or/ [BIT_XOR] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the bitwise XOR of all bits in expr. The calculation\nis performed with 64-bit (BIGINT) precision. It is an\naggregate function, and so can be used with the GROUP BY\nclause.\n \nFrom MariaDB 10.2.0, BIT_XOR() can be used as a window\nfunction.\n \n\nCREATE TABLE vals (x INT);\n \nINSERT INTO vals VALUES(111),(110),(100);\n \nSELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals;\n \n+------------+-----------+------------+\n| BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+------------+-----------+------------+\n| 100 | 111 | 101 |\n+------------+-----------+------------+\n \nAs an aggregate function:\n \nCREATE TABLE vals2 (category VARCHAR(1), x INT);\n \nINSERT INTO vals2 VALUES\n (''a'',111),(''a'',110),(''a'',100),\n (''b'',''000''),(''b'',001),(''b'',011);\n \nSELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) \n FROM vals GROUP BY category;\n \n+----------+------------+-----------+------------+\n| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+----------+------------+-----------+------------+\n| a | 100 | 111 | 101 |\n| b | 0 | 11 | 10 |\n+----------+------------+-----------+------------+ +description=Returns the bitwise XOR of all bits in expr. The calculation is performed with\n64-bit (BIGINT) precision. It is an aggregate function, and so can be used\nwith the GROUP BY clause.\n\nIf no rows match, BIT_XOR will return a value with all bits set to 0. NULL\nvalues have no effect on the result unless all results are NULL, which is\ntreated as no match.\n\nBIT_XOR can be used as a window function with the addition of the over_clause.\n\nExamples\n--------\n\nCREATE TABLE vals (x INT);\n\nINSERT INTO vals VALUES(111),(110),(100);\n\nSELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals;\n+------------+-----------+------------+\n| BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+------------+-----------+------------+\n| 100 | 111 | 101 |\n+------------+-----------+------------+\n\nAs an aggregate function:\n\nCREATE TABLE vals2 (category VARCHAR(1), x INT);\n\nINSERT INTO vals2 VALUES\n ('a',111),('a',110),('a',100),\n ('b','000'),('b',001),('b',011);\n\nSELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) \n FROM vals GROUP BY category;\n+----------+------------+-----------+------------+\n| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) |\n+----------+------------+-----------+------------+\n| a | 100 | 111 | 101 |\n| b | 0 | 11 | 10 |\n+----------+------------+-----------+------------+\n\nNo match:\n\nSELECT BIT_XOR(NULL);\n+---------------+\n| BIT_XOR(NULL) |\n+---------------+\n| 0 |\n+---------------+\n\nURL: https://mariadb.com/kb/en/bit_xor/ +[BLOB] +declaration=M +category=Data Types +description=A BLOB column with a maximum length of 65,535 (216 - 1) bytes. Each BLOB value\nis stored using a two-byte length prefix that indicates the number of bytes in\nthe value.\n\nAn optional length M can be given for this type. If this is done, MariaDB\ncreates the column as the smallest BLOB type large enough to hold values M\nbytes long.\n\nBLOBS can also be used to store dynamic columns.\n\nBLOB and TEXT columns can both be assigned a DEFAULT value.\n\nIndexing\n--------\n\nMariaDB starting with 10.4\n--------------------------\nFrom MariaDB 10.4, it is possible to set a unique index on a column that uses\nthe BLOB data type. In previous releases this was not possible, as the index\nwould only guarantee the uniqueness of a fixed number of characters.\n\nOracle Mode\n-----------\n\nIn Oracle mode from MariaDB 10.3, BLOB is a synonym for LONGBLOB.\n\nURL: https://mariadb.com/kb/en/blob/ [CAST] declaration=expr AS type category=String Functions -description=The CAST() function takes a value of one type and produces a\nvalue of another type, similar to the CONVERT() function.\nFor more information, see the description of CONVERT(). \n \nThe main difference between the CAST() and CONVERT() is that\nCONVERT(expr,type) is ODBC syntax while CAST(expr as type)\nand CONVERT(... USING ...) are SQL92 syntax.\n \nIn MariaDB 10.4 and later, you can use the CAST() function\nwith the INTERVAL keyword.\n \nUntil MariaDB 5.5.31, X''HHHH'', the standard SQL syntax for\nbinary string literals, erroneously worked in the same way\nas 0xHHHH. In 5.5.31 it was intentionally changed to behave\nas a string in all contexts (and never as a number).\n \nThis introduces an incompatibility with previous versions of\nMariaDB, and all versions of MySQL (see the example below). \n \n\nSimple casts:\n \nSELECT CAST("abc" AS BINARY);\nSELECT CAST("1" AS UNSIGNED INTEGER);\nSELECT CAST(123 AS CHAR CHARACTER SET utf8)\n \nNote that when one casts to CHAR without specifying the\ncharacter set, the collation_connection character set\ncollation will be used. When used with CHAR CHARACTER SET,\nthe default collation for that character set will be used.\n \nSELECT COLLATION(CAST(123 AS CHAR));\n+------------------------------+\n| COLLATION(CAST(123 AS CHAR)) |\n+------------------------------+\n| latin1_swedish_ci |\n+------------------------------+\n \nSELECT COLLATION(CAST(123 AS CHAR CHARACTER SET utf8));\n+-------------------------------------------------+\n| COLLATION(CAST(123 AS CHAR CHARACTER SET utf8)) |\n+-------------------------------------------------+\n| utf8_general_ci |\n+-------------------------------------------------+\n \nIf you also want to change the collation, you have to use\nthe COLLATE operator:\n \nSELECT COLLATION(CAST(123 AS CHAR CHARACTER SET utf8) \n COLLATE utf8_unicode_ci);\n+-------------------------------------------------------------------------+\n| COLLATION(CAST(123 AS CHAR CHARACTER SET utf8) COLLATE\nutf8_unicode_ci) |\n+-------------------------------------------------------------------------+\n| utf8_unicode_ci |\n+-------------------------------------------------------------------------+\n \nUsing CAST() to order an ENUM field as a CHAR rather than\nthe internal numerical value:\n \nCREATE TABLE enum_list (enum_field enum(''c'',''a'',''b''));\n \nINSERT INTO enum_list (enum_field) \nVALUES(''c''),(''a''),(''c''),(''b'');\n \nSELECT * FROM enum_list \nORDER BY enum_field;\n \n+------------+\n| enum_field |\n+------------+\n| c |\n| c |\n| a |\n| b |\n+------------+\n \nSELECT * FROM enum_list \nORDER BY CAST(enum_field AS CHAR);\n+------------+\n| enum_field |\n+------------+\n| a |\n| b |\n| c |\n| c |\n+------------+\n \nFrom MariaDB 5.5.31, the following will trigger warnings,\nsince x''aa'' and ''X''aa'' no longer behave as a number.\nPreviously, and in all versions of MySQL, no warnings are\ntriggered since they did erroneously behave as a number:\n \nSELECT CAST(0xAA AS UNSIGNED), CAST(x''aa'' AS UNSIGNED),\nCAST(X''aa'' AS UNSIGNED);\n+------------------------+-------------------------+-------------------------+\n| CAST(0xAA AS UNSIGNED) | CAST(x''aa'' AS UNSIGNED) |\nCAST(X''aa'' AS UNSIGNED) |\n+------------------------+-------------------------+-------------------------+\n| 170 | 0 | 0 |\n+------------------------+-------------------------+-------------------------+\n1 row in set, 2 warnings (0.00 sec)\n \nWarning (Code 1292): Truncated incorrect INTEGER value:\n''\xAA''\nWarning (Code 1292): Truncated incorrect INTEGER value:\n''\xAA''\n \nCasting to intervals:\n \nSELECT CAST(2019-01-04 INTERVAL AS DAY_SECOND(2)) AS\n"Cast";\n \n+-------------+\n| Cast |\n+-------------+\n| 00:20:17.00 |\n+-------------+ -[CEILING] +description=The CAST() function takes a value of one type and produces a value of another\ntype, similar to the CONVERT() function.\n\nThe type can be one of the following values:\n\n* BINARY\n* CHAR\n* DATE\n* DATETIME\n* DECIMAL[(M[,D])]\n* DOUBLE\n* FLOAT (from MariaDB 10.4.5)\n* INTEGER\nShort for SIGNED INTEGER\n\n* SIGNED [INTEGER]\n* UNSIGNED [INTEGER]\n* TIME\n* VARCHAR (in Oracle mode, from MariaDB 10.3)\n\nThe main difference between CAST and CONVERT() is that CONVERT(expr,type) is\nODBC syntax while CAST(expr as type) and CONVERT(... USING ...) are SQL92\nsyntax.\n\nIn MariaDB 10.4 and later, you can use the CAST() function with the INTERVAL\nkeyword.\n\nUntil MariaDB 5.5.31, X'HHHH', the standard SQL syntax for binary string\nliterals, erroneously worked in the same way as 0xHHHH. In 5.5.31 it was\nintentionally changed to behave as a string in all contexts (and never as a\nnumber).\n\nThis introduced an incompatibility with previous versions of MariaDB, and all\nversions of MySQL (see the example below).\n\nExamples\n--------\n\nSimple casts:\n\nSELECT CAST("abc" AS BINARY);\nSELECT CAST("1" AS UNSIGNED INTEGER);\nSELECT CAST(123 AS CHAR CHARACTER SET utf8)\n\nNote that when one casts to CHAR without specifying the character set, the\ncollation_connection character set collation will be used. When used with CHAR\nCHARACTER SET, the default collation for that character set will be used.\n\nSELECT COLLATION(CAST(123 AS CHAR));\n+------------------------------+\n ... +[CEIL] declaration=X category=Numeric Functions -description=Returns the smallest integer value not less than X.\n \n\nSELECT CEILING(1.23);\n+---------------+\n| CEILING(1.23) |\n+---------------+\n| 2 |\n+---------------+\n \nSELECT CEILING(-1.23);\n+----------------+\n| CEILING(-1.23) |\n+----------------+\n| -1 |\n+----------------+ -[CEIL] +description=CEIL() is a synonym for CEILING().\n\nURL: https://mariadb.com/kb/en/ceil/ +[CEILING] declaration=X category=Numeric Functions -description=CEIL() is a synonym for CEILING(). -[CHARACTER_LENGTH] -declaration=str -category=String Functions -description=CHARACTER_LENGTH() is a synonym for CHAR_LENGTH(). +description=Returns the smallest integer value not less than X.\n\nExamples\n--------\n\nSELECT CEILING(1.23);\n+---------------+\n| CEILING(1.23) |\n+---------------+\n| 2 |\n+---------------+\n\nSELECT CEILING(-1.23);\n+----------------+\n| CEILING(-1.23) |\n+----------------+\n| -1 |\n+----------------+\n\nURL: https://mariadb.com/kb/en/ceiling/ +[CHAR] +declaration=M +category=Data Types +description=A fixed-length string that is always right-padded with spaces to the specified\nlength when stored. M represents the column length in characters. The range of\nM is 0 to 255. If M is omitted, the length is 1.\n\nCHAR(0) columns can contain 2 values: an empty string or NULL. Such columns\ncannot be part of an index. The CONNECT storage engine does not support\nCHAR(0).\n\nNote: Trailing spaces are removed when CHAR values are retrieved unless the\nPAD_CHAR_TO_FULL_LENGTH SQL mode is enabled.\n\nBefore MariaDB 10.2, all collations were of type PADSPACE, meaning that CHAR\n(as well as VARCHAR and TEXT) values are compared without regard for trailing\nspaces. This does not apply to the LIKE pattern-matching operator, which takes\ninto account trailing spaces.\n\nIf a unique index consists of a column where trailing pad characters are\nstripped or ignored, inserts into that column where values differ only by the\nnumber of trailing pad characters will result in a duplicate-key error.\n\nExamples\n--------\n\nTrailing spaces:\n\nCREATE TABLE strtest (c CHAR(10));\nINSERT INTO strtest VALUES('Maria ');\n\nSELECT c='Maria',c='Maria ' FROM strtest;\n+-----------+--------------+\n| c='Maria' | c='Maria ' |\n+-----------+--------------+\n| 1 | 1 |\n+-----------+--------------+\n\nSELECT c LIKE 'Maria',c LIKE 'Maria ' FROM strtest;\n+----------------+-------------------+\n| c LIKE 'Maria' | c LIKE 'Maria ' |\n+----------------+-------------------+\n| 1 | 0 |\n+----------------+-------------------+\n\nNO PAD Collations\n-----------------\n\nNO PAD collations regard trailing spaces as normal characters. You can get a\nlist of all NO PAD collations by querying the Information Schema Collations\ntable, for example:\n\nSELECT collation_name FROM information_schema.collations \n ... [CHARSET] declaration=str category=Information Functions -description=Returns the character set of the string argument. If str is\nnot a string, it is considered as a binary string (so the\nfunction returns ''binary''). This applies to NULL, too. The\nreturn value is a string in the utf8 character set.\n \n\nSELECT CHARSET(''abc'');\n+----------------+\n| CHARSET(''abc'') |\n+----------------+\n| latin1 |\n+----------------+\n \nSELECT CHARSET(CONVERT(''abc'' USING utf8));\n+------------------------------------+\n| CHARSET(CONVERT(''abc'' USING utf8)) |\n+------------------------------------+\n| utf8 |\n+------------------------------------+\n \nSELECT CHARSET(USER());\n+-----------------+\n| CHARSET(USER()) |\n+-----------------+\n| utf8 |\n+-----------------+ +description=Returns the character set of the string argument. If str is not a string, it\nis considered as a binary string (so the function returns 'binary'). This\napplies to NULL, too. The return value is a string in the utf8 character set.\n\nExamples\n--------\n\nSELECT CHARSET('abc');\n+----------------+\n| CHARSET('abc') |\n+----------------+\n| latin1 |\n+----------------+\n\nSELECT CHARSET(CONVERT('abc' USING utf8));\n+------------------------------------+\n| CHARSET(CONVERT('abc' USING utf8)) |\n+------------------------------------+\n| utf8 |\n+------------------------------------+\n\nSELECT CHARSET(USER());\n+-----------------+\n| CHARSET(USER()) |\n+-----------------+\n| utf8 |\n+-----------------+\n\nURL: https://mariadb.com/kb/en/charset/ [CHAR_LENGTH] declaration=str category=String Functions -description=Returns the length of the given string argument, measured in\ncharacters. A multi-byte character counts as a single\ncharacter. This means that for a string containing five\ntwo-byte characters, LENGTH() (or OCTET_LENGTH() in Oracle\nmode) returns 10, whereas CHAR_LENGTH() returns 5. If the\nargument is NULL, it returns NULL. \n \nIf the argument is not a string value, it is converted into\na string.\n \nIt is synonymous with the CHARACTER_LENGTH() function.\n \nUntil MariaDB 10.3.1, returns MYSQL_TYPE_LONGLONG, or\nbigint(10), in all cases. From MariaDB 10.3.1, returns\nMYSQL_TYPE_LONG, or int(10), when the result would fit\nwithin 32-bits.\n \n\nSELECT CHAR_LENGTH(''MariaDB'');\n+------------------------+\n| CHAR_LENGTH(''MariaDB'') |\n+------------------------+\n| 7 |\n+------------------------+\n \nSELECT CHAR_LENGTH(''?'');\n+-------------------+\n| CHAR_LENGTH(''?'') |\n+-------------------+\n| 1 |\n+-------------------+ +description=Returns the length of the given string argument, measured in characters. A\nmulti-byte character counts as a single character. This means that for a\nstring containing five two-byte characters, LENGTH() (or OCTET_LENGTH() in\nOracle mode) returns 10, whereas CHAR_LENGTH() returns 5. If the argument is\nNULL, it returns NULL.\n\nIf the argument is not a string value, it is converted into a string.\n\nIt is synonymous with the CHARACTER_LENGTH() function.\n\nExamples\n--------\n\nSELECT CHAR_LENGTH('MariaDB');\n+------------------------+\n| CHAR_LENGTH('MariaDB') |\n+------------------------+\n| 7 |\n+------------------------+\n\nWhen Oracle mode from MariaDB 10.3 is not set:\n\nSELECT CHAR_LENGTH('π'), LENGTH('π'), LENGTHB('π'), OCTET_LENGTH('π');\n+-------------------+--------------+---------------+--------------------+\n| CHAR_LENGTH('π') | LENGTH('π') | LENGTHB('π') | OCTET_LENGTH('π') |\n+-------------------+--------------+---------------+--------------------+\n| 1 | 2 | 2 | 2 |\n+-------------------+--------------+---------------+--------------------+\n\nIn Oracle mode from MariaDB 10.3:\n\nSELECT CHAR_LENGTH('π'), LENGTH('π'), LENGTHB('π'), OCTET_LENGTH('π');\n+-------------------+--------------+---------------+--------------------+\n| CHAR_LENGTH('π') | LENGTH('π') | LENGTHB('π') | OCTET_LENGTH('π') |\n+-------------------+--------------+---------------+--------------------+\n| 1 | 1 | 2 | 2 |\n+-------------------+--------------+---------------+--------------------+\n\nURL: https://mariadb.com/kb/en/char_length/ [CHR] declaration=N category=String Functions -description=CHR() interprets each argument N as an integer and returns a\nVARCHAR(1) string consisting of the character given by the\ncode values of the integer. The character set and collation\nof the string are set according to the values of the\ncharacter_set_database and collation_database system\nvariables.\n \nCHR() is similar to the CHAR() function, but only accepts a\nsingle argument.\n \nCHR() is available in all sql_modes.\n \n\nSELECT CHR(67);\n+---------+\n| CHR(67) |\n+---------+\n| C |\n+---------+\n \nSELECT CHR(''67'');\n+-----------+\n| CHR(''67'') |\n+-----------+\n| C |\n+-----------+\n \nSELECT CHR(''C'');\n+----------+\n| CHR(''C'') |\n+----------+\n| |\n+----------+\n1 row in set, 1 warning (0.000 sec)\n \nSHOW WARNINGS;\n \n+---------+------+----------------------------------------+\n| Level | Code | Message |\n+---------+------+----------------------------------------+\n| Warning | 1292 | Truncated incorrect INTEGER value: ''C''\n|\n+---------+------+----------------------------------------+ +description=CHR() interprets each argument N as an integer and returns a VARCHAR(1) string\nconsisting of the character given by the code values of the integer. The\ncharacter set and collation of the string are set according to the values of\nthe character_set_database and collation_database system variables.\n\nCHR() is similar to the CHAR() function, but only accepts a single argument.\n\nCHR() is available in all sql_modes.\n\nExamples\n--------\n\nSELECT CHR(67);\n+---------+\n| CHR(67) |\n+---------+\n| C |\n+---------+\n\nSELECT CHR('67');\n+-----------+\n| CHR('67') |\n+-----------+\n| C |\n+-----------+\n\nSELECT CHR('C');\n+----------+\n| CHR('C') |\n+----------+\n| |\n+----------+\n1 row in set, 1 warning (0.000 sec)\n\nSHOW WARNINGS;\n+---------+------+----------------------------------------+\n| Level | Code | Message |\n+---------+------+----------------------------------------+\n| Warning | 1292 | Truncated incorrect INTEGER value: 'C' |\n+---------+------+----------------------------------------+\n\nURL: https://mariadb.com/kb/en/chr/ [COALESCE] declaration=value,... category=Comparison Operators -description=Returns the first non-NULL value in the list, or NULL if\nthere are no\nnon-NULL values. At least one parameter must be passed.\n \nSee also NULL Values in MariaDB.\n \n\nSELECT COALESCE(NULL,1);\n+------------------+\n| COALESCE(NULL,1) |\n+------------------+\n| 1 |\n+------------------+\n \nSELECT COALESCE(NULL,NULL,NULL);\n+--------------------------+\n| COALESCE(NULL,NULL,NULL) |\n+--------------------------+\n| NULL |\n+--------------------------+\n \nWhen two arguments are given, COALESCE() is the same as\nIFNULL():\n \nSET @a=NULL, @b=1;\n \nSELECT COALESCE(@a, @b), IFNULL(@a, @b);\n+------------------+----------------+\n| COALESCE(@a, @b) | IFNULL(@a, @b) |\n+------------------+----------------+\n| 1 | 1 |\n+------------------+----------------+\n \nHex type confusion:\n \nCREATE TABLE t1 (a INT, b VARCHAR(10));\nINSERT INTO t1 VALUES (0x31, 0x61),(COALESCE(0x31),\nCOALESCE(0x61));\n \nSELECT * FROM t1;\n \n+------+------+\n| a | b |\n+------+------+\n| 49 | a |\n| 1 | a |\n+------+------+\n \nThe reason for the differing results above is that when 0x31\nis inserted directly to the column, it''s treated as a\nnumber (see Hexadecimal Literals), while when 0x31 is passed\nto COALESCE(), it''s treated as a string, because:\nHEX values have a string data type by default.\nCOALESCE() has the same data type as the argument. +description=Returns the first non-NULL value in the list, or NULL if there are no non-NULL\nvalues. At least one parameter must be passed.\n\nThe function is useful when substituting a default value for null values when\ndisplaying data.\n\nSee also NULL Values in MariaDB.\n\nExamples\n--------\n\nSELECT COALESCE(NULL,1);\n+------------------+\n| COALESCE(NULL,1) |\n+------------------+\n| 1 |\n+------------------+\n\nSELECT COALESCE(NULL,NULL,NULL);\n+--------------------------+\n| COALESCE(NULL,NULL,NULL) |\n+--------------------------+\n| NULL |\n+--------------------------+\n\nWhen two arguments are given, COALESCE() is the same as IFNULL():\n\nSET @a=NULL, @b=1;\n\nSELECT COALESCE(@a, @b), IFNULL(@a, @b);\n+------------------+----------------+\n| COALESCE(@a, @b) | IFNULL(@a, @b) |\n+------------------+----------------+\n| 1 | 1 |\n+------------------+----------------+\n\nHex type confusion:\n\nCREATE TABLE t1 (a INT, b VARCHAR(10));\nINSERT INTO t1 VALUES (0x31, 0x61),(COALESCE(0x31), COALESCE(0x61));\n\nSELECT * FROM t1;\n+------+------+\n| a | b |\n+------+------+\n| 49 | a |\n| 1 | a |\n+------+------+\n\nThe reason for the differing results above is that when 0x31 is inserted\n ... [COERCIBILITY] declaration=str category=Information Functions -description=Returns the collation coercibility value of the string\nargument. Coercibility defines what will be converted to\nwhat in case of collation conflict, with an expression with\nhigher coercibility being converted to the collation of an\nexpression with lower coercibility.\n \nCoercibility | Description | Example | \n \n0 | Explicit | Value using a COLLATE clause | \n \n1 | No collation | Concatenated strings using different\ncollations | \n \n2 | Implicit | Column value | \n \n3 | Constant | USER() return value | \n \n4 | Coercible | Literal string | \n \n5 | Ignorable | NULL or derived from NULL | \n \n\nSELECT COERCIBILITY(''abc'' COLLATE latin1_swedish_ci);\n+-----------------------------------------------+\n| COERCIBILITY(''abc'' COLLATE latin1_swedish_ci) |\n+-----------------------------------------------+\n| 0 |\n+-----------------------------------------------+\n \nSELECT COERCIBILITY(USER());\n+----------------------+\n| COERCIBILITY(USER()) |\n+----------------------+\n| 3 |\n+----------------------+\n \nSELECT COERCIBILITY(''abc'');\n+---------------------+\n| COERCIBILITY(''abc'') |\n+---------------------+\n| 4 |\n+---------------------+ +description=Returns the collation coercibility value of the string argument. Coercibility\ndefines what will be converted to what in case of collation conflict, with an\nexpression with higher coercibility being converted to the collation of an\nexpression with lower coercibility.\n\n+-----------------------------+---------------------------+------------------+\n| Coercibility | Description | Example |\n+-----------------------------+---------------------------+------------------+\n| 0 | Explicit | Value using a |\n| | | COLLATE clause |\n+-----------------------------+---------------------------+------------------+\n| 1 | No collation | Concatenated |\n| | | strings using |\n| | | different |\n| | | collations |\n+-----------------------------+---------------------------+------------------+\n| 2 | Implicit | A string data |\n| | | type column |\n| | | value, CAST to |\n| | | a string data |\n| | | type |\n+-----------------------------+---------------------------+------------------+\n| 3 | System constant | DATABASE(), |\n| | | USER() return |\n| | | value |\n+-----------------------------+---------------------------+------------------+\n| 4 | Coercible | Literal string |\n+-----------------------------+---------------------------+------------------+\n| 5 | Numeric | Numeric and |\n| | | temporal values |\n+-----------------------------+---------------------------+------------------+\n| 6 | Ignorable | NULL or derived |\n| | | from NULL |\n+-----------------------------+---------------------------+------------------+\n\nExamples\n--------\n\nSELECT COERCIBILITY('abc' COLLATE latin1_swedish_ci);\n+-----------------------------------------------+\n| COERCIBILITY('abc' COLLATE latin1_swedish_ci) |\n+-----------------------------------------------+\n| 0 |\n+-----------------------------------------------+\n\nSELECT COERCIBILITY(CAST(1 AS CHAR));\n+-------------------------------+\n| COERCIBILITY(CAST(1 AS CHAR)) |\n+-------------------------------+\n| 2 |\n ... [COLLATION] declaration=str category=Information Functions -description=Returns the collation of the string argument. If str is not\na string, it is considered as a binary string (so the\nfunction returns ''binary''). This applies to NULL, too. The\nreturn value is a string in the utf8 character set.\n \nSee Character Sets and Collations.\n \n\nSELECT COLLATION(''abc'');\n+-------------------+\n| COLLATION(''abc'') |\n+-------------------+\n| latin1_swedish_ci |\n+-------------------+\n \nSELECT COLLATION(_utf8''abc'');\n+-----------------------+\n| COLLATION(_utf8''abc'') |\n+-----------------------+\n| utf8_general_ci |\n+-----------------------+ -[COLUMN_ADD1] -name=COLUMN_ADD +description=Returns the collation of the string argument. If str is not a string, it is\nconsidered as a binary string (so the function returns 'binary'). This applies\nto NULL, too. The return value is a string in the utf8 character set.\n\nSee Character Sets and Collations.\n\nExamples\n--------\n\nSELECT COLLATION('abc');\n+-------------------+\n| COLLATION('abc') |\n+-------------------+\n| latin1_swedish_ci |\n+-------------------+\n\nSELECT COLLATION(_utf8'abc');\n+-----------------------+\n| COLLATION(_utf8'abc') |\n+-----------------------+\n| utf8_general_ci |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/collation/ +[COLUMN_ADD] declaration=dyncol_blob, column_nr, value [as type], [column_nr, value [as type]]... category=Dynamic Column Functions -description=Adds or updates dynamic columns.\ndyncol_blob must be either a valid dynamic columns blob (for\nexample, COLUMN_CREATE returns such blob), or an empty\nstring.\ncolumn_name specifies the name of the column to be added. If\ndyncol_blob already has a column with this name, it will be\noverwritten.\nvalue specifies the new value for the column. Passing a NULL\nvalue will cause the column to be deleted.\nas type is optional. See #datatypes section for a discussion\nabout types.\n \nThe return value is a dynamic column blob after the\nmodifications.\n \n\n-- MariaDB 5.3+:\nUPDATE tbl SET dyncol_blob=COLUMN_ADD(dyncol_blob, 1\n/*column id*/, "value") WHERE id=1;\n \n-- MariaDB 10.0.1+:\nUPDATE t1 SET dyncol_blob=COLUMN_ADD(dyncol_blob,\n"column_name", "value") WHERE id=1;\n \nNote: COLUMN_ADD() is a regular function (just like\nCONCAT()), hence, in order to update the value in the table\nyou have to use the UPDATE ... SET\ndynamic_col=COLUMN_ADD(dynamic_col,\n....) pattern. -[COLUMN_ADD2] -name=COLUMN_ADD -declaration=dyncol_blob, column_name, value [as type], [column_name, value [as type]]... -category=Dynamic Column Functions -description=Adds or updates dynamic columns.\ndyncol_blob must be either a valid dynamic columns blob (for\nexample, COLUMN_CREATE returns such blob), or an empty\nstring.\ncolumn_name specifies the name of the column to be added. If\ndyncol_blob already has a column with this name, it will be\noverwritten.\nvalue specifies the new value for the column. Passing a NULL\nvalue will cause the column to be deleted.\nas type is optional. See #datatypes section for a discussion\nabout types.\n \nThe return value is a dynamic column blob after the\nmodifications.\n \n\n-- MariaDB 5.3+:\nUPDATE tbl SET dyncol_blob=COLUMN_ADD(dyncol_blob, 1\n/*column id*/, "value") WHERE id=1;\n \n-- MariaDB 10.0.1+:\nUPDATE t1 SET dyncol_blob=COLUMN_ADD(dyncol_blob,\n"column_name", "value") WHERE id=1;\n \nNote: COLUMN_ADD() is a regular function (just like\nCONCAT()), hence, in order to update the value in the table\nyou have to use the UPDATE ... SET\ndynamic_col=COLUMN_ADD(dynamic_col,\n....) pattern. +description=Adds or updates dynamic columns.\n\n* dyncol_blob must be either a valid dynamic columns blob (for example,\nCOLUMN_CREATE returns such blob), or an empty string.\n* column_name specifies the name of the column to be added. If dyncol_blob\nalready has a column with this name, it will be overwritten.\n* value specifies the new value for the column. Passing a NULL value will\ncause the column to be deleted.\n* as type is optional. See #datatypes section for a discussion about types.\n\nThe return value is a dynamic column blob after the modifications.\n\nExamples\n--------\n\nUPDATE t1 SET dyncol_blob=COLUMN_ADD(dyncol_blob, "column_name", "value")\nWHERE id=1;\n\nNote: COLUMN_ADD() is a regular function (just like CONCAT()), hence, in order\nto update the value in the table you have to use the UPDATE ... SET\ndynamic_col=COLUMN_ADD(dynamic_col, ....) pattern.\n\nURL: https://mariadb.com/kb/en/column_add/ [COLUMN_CHECK] declaration=dyncol_blob category=Dynamic Column Functions -description=Check if dyncol_blob is a valid packed dynamic columns blob.\nReturn value of 1 means the blob is valid, return value of 0\nmeans it is not.\n \nRationale:\nNormally, one works with valid dynamic column blobs.\nFunctions like COLUMN_CREATE, COLUMN_ADD, COLUMN_DELETE\nalways return valid dynamic column blobs. However, if a\ndynamic column blob is accidentally truncated, or transcoded\nfrom one character set to another, it will be corrupted.\nThis function can be used to check if a value in a blob\nfield is a valid dynamic column blob. -[COLUMN_CREATE1] -name=COLUMN_CREATE +description=Check if dyncol_blob is a valid packed dynamic columns blob. Return value of 1\nmeans the blob is valid, return value of 0 means it is not.\n\nRationale: Normally, one works with valid dynamic column blobs. Functions like\nCOLUMN_CREATE, COLUMN_ADD, COLUMN_DELETE always return valid dynamic column\nblobs. However, if a dynamic column blob is accidentally truncated, or\ntranscoded from one character set to another, it will be corrupted. This\nfunction can be used to check if a value in a blob field is a valid dynamic\ncolumn blob.\n\nURL: https://mariadb.com/kb/en/column_check/ +[COLUMN_CREATE] declaration=column_nr, value [as type], [column_nr, value [as type]]... category=Dynamic Column Functions -description=Returns a dynamic columns blob that stores the specified\ncolumns with values.\n \nThe return value is suitable for \nstoring in a table\nfurther modification with other dynamic columns functions\n \nThe as type part allows one to specify the value type. In\nmost cases,\nthis is redundant because MariaDB will be able to deduce the\ntype of the\nvalue. Explicit type specification may be needed when the\ntype of the value is\nnot apparent. For example, a literal ''2012-12-01'' has a\nCHAR type by\ndefault, one will need to specify ''2012-12-01'' AS DATE to\nhave it stored as\na date. See Dynamic Columns:Datatypes for further details.\n \n\n-- MariaDB 5.3+:\nINSERT INTO tbl SET dyncol_blob=COLUMN_CREATE(1 /*column\nid*/, "value");\n-- MariaDB 10.0.1+:\nINSERT INTO tbl SET\ndyncol_blob=COLUMN_CREATE("column_name", "value"); -[COLUMN_CREATE2] -name=COLUMN_CREATE -declaration=column_name, value [as type], [column_name, value [as type]]... -category=Dynamic Column Functions -description=Returns a dynamic columns blob that stores the specified\ncolumns with values.\n \nThe return value is suitable for \nstoring in a table\nfurther modification with other dynamic columns functions\n \nThe as type part allows one to specify the value type. In\nmost cases,\nthis is redundant because MariaDB will be able to deduce the\ntype of the\nvalue. Explicit type specification may be needed when the\ntype of the value is\nnot apparent. For example, a literal ''2012-12-01'' has a\nCHAR type by\ndefault, one will need to specify ''2012-12-01'' AS DATE to\nhave it stored as\na date. See Dynamic Columns:Datatypes for further details.\n \n\n-- MariaDB 5.3+:\nINSERT INTO tbl SET dyncol_blob=COLUMN_CREATE(1 /*column\nid*/, "value");\n-- MariaDB 10.0.1+:\nINSERT INTO tbl SET\ndyncol_blob=COLUMN_CREATE("column_name", "value"); -[COLUMN_DELETE1] -name=COLUMN_DELETE +description=Returns a dynamic columns blob that stores the specified columns with values.\n\nThe return value is suitable for\n\n* storing in a table\n* further modification with other dynamic columns functions\n\nThe as type part allows one to specify the value type. In most cases, this is\nredundant because MariaDB will be able to deduce the type of the value.\nExplicit type specification may be needed when the type of the value is not\napparent. For example, a literal '2012-12-01' has a CHAR type by default, one\nwill need to specify '2012-12-01' AS DATE to have it stored as a date. See\nDynamic Columns:Datatypes for further details.\n\nExamples\n--------\n\nINSERT INTO tbl SET dyncol_blob=COLUMN_CREATE("column_name", "value");\n\nURL: https://mariadb.com/kb/en/column_create/ +[COLUMN_DELETE] declaration=dyncol_blob, column_nr, column_nr... category=Dynamic Column Functions -description=Deletes a dynamic column with the specified name. Multiple\nnames can be given. The return value is a dynamic column\nblob after the modification. -[COLUMN_DELETE2] -name=COLUMN_DELETE -declaration=dyncol_blob, column_name, column_name... -category=Dynamic Column Functions -description=Deletes a dynamic column with the specified name. Multiple\nnames can be given. The return value is a dynamic column\nblob after the modification. -[COLUMN_EXISTS1] -name=COLUMN_EXISTS +description=Deletes a dynamic column with the specified name. Multiple names can be given.\nThe return value is a dynamic column blob after the modification.\n\nURL: https://mariadb.com/kb/en/column_delete/ +[COLUMN_EXISTS] declaration=dyncol_blob, column_nr category=Dynamic Column Functions -description=Checks if a column with name column_name exists in\ndyncol_blob. If yes, return 1, otherwise return 0. See\ndynamic columns for more information. -[COLUMN_EXISTS2] -name=COLUMN_EXISTS -declaration=dyncol_blob, column_name -category=Dynamic Column Functions -description=Checks if a column with name column_name exists in\ndyncol_blob. If yes, return 1, otherwise return 0. See\ndynamic columns for more information. -[COLUMN_GET1] -name=COLUMN_GET +description=Checks if a column with name column_name exists in dyncol_blob. If yes, return\n1, otherwise return 0. See dynamic columns for more information.\n\nURL: https://mariadb.com/kb/en/column_exists/ +[COLUMN_GET] declaration=dyncol_blob, column_nr as type category=Dynamic Column Functions -description=Gets the value of a dynamic column by its name. If no column\nwith the given name exists, NULL will be returned.\n \ncolumn_name as type requires that one specify the datatype\nof the dynamic column they are reading. \n \nThis may seem counter-intuitive: why would one need to\nspecify which datatype they''re retrieving? Can''t the\ndynamic columns system figure the datatype from the data\nbeing stored?\n \nThe answer is: SQL is a statically-typed language. The SQL\ninterpreter needs to know the datatypes of all expressions\nbefore the query is run (for example, when one is using\nprepared statements and runs "select COLUMN_GET(...)", the\nprepared statement API requires the server to inform the\nclient about the datatype of the column being read before\nthe query is executed and the server can see what datatype\nthe column actually has).\n \nA note about lengths\n \nIf you''re running queries like:\n \nSELECT COLUMN_GET(blob, ''colname'' as CHAR) ...\n \nwithout specifying a maximum length (i.e. using #as CHAR#,\nnot as CHAR(n)), MariaDB will report the maximum length of\nthe resultset column to be 53,6870,911 for MariaDB\n5.3-10.0.0 and 16,777,216 for MariaDB 10.0.1+. This may\ncause excessive memory usage in some client libraries,\nbecause they try to pre-allocate a buffer of maximum\nresultset width. To avoid this problem, use CHAR(n) whenever\nyou''re using COLUMN_GET in the select list.\n \nSee Dynamic Columns:Datatypes for more information about\ndatatypes. -[COLUMN_GET2] -name=COLUMN_GET -declaration=dyncol_blob, column_name as type -category=Dynamic Column Functions -description=Gets the value of a dynamic column by its name. If no column\nwith the given name exists, NULL will be returned.\n \ncolumn_name as type requires that one specify the datatype\nof the dynamic column they are reading. \n \nThis may seem counter-intuitive: why would one need to\nspecify which datatype they''re retrieving? Can''t the\ndynamic columns system figure the datatype from the data\nbeing stored?\n \nThe answer is: SQL is a statically-typed language. The SQL\ninterpreter needs to know the datatypes of all expressions\nbefore the query is run (for example, when one is using\nprepared statements and runs "select COLUMN_GET(...)", the\nprepared statement API requires the server to inform the\nclient about the datatype of the column being read before\nthe query is executed and the server can see what datatype\nthe column actually has).\n \nA note about lengths\n \nIf you''re running queries like:\n \nSELECT COLUMN_GET(blob, ''colname'' as CHAR) ...\n \nwithout specifying a maximum length (i.e. using #as CHAR#,\nnot as CHAR(n)), MariaDB will report the maximum length of\nthe resultset column to be 53,6870,911 for MariaDB\n5.3-10.0.0 and 16,777,216 for MariaDB 10.0.1+. This may\ncause excessive memory usage in some client libraries,\nbecause they try to pre-allocate a buffer of maximum\nresultset width. To avoid this problem, use CHAR(n) whenever\nyou''re using COLUMN_GET in the select list.\n \nSee Dynamic Columns:Datatypes for more information about\ndatatypes. +description=Gets the value of a dynamic column by its name. If no column with the given\nname exists, NULL will be returned.\n\ncolumn_name as type requires that one specify the datatype of the dynamic\ncolumn they are reading.\n\nThis may seem counter-intuitive: why would one need to specify which datatype\nthey're retrieving? Can't the dynamic columns system figure the datatype from\nthe data being stored?\n\nThe answer is: SQL is a statically-typed language. The SQL interpreter needs\nto know the datatypes of all expressions before the query is run (for example,\nwhen one is using prepared statements and runs "select COLUMN_GET(...)", the\nprepared statement API requires the server to inform the client about the\ndatatype of the column being read before the query is executed and the server\ncan see what datatype the column actually has).\n\nLengths\n-------\n\nIf you're running queries like:\n\nSELECT COLUMN_GET(blob, 'colname' as CHAR) ...\n\nwithout specifying a maximum length (i.e. using as CHAR, not as CHAR(n)),\nMariaDB will report the maximum length of the resultset column to be\n16,777,216. This may cause excessive memory usage in some client libraries,\nbecause they try to pre-allocate a buffer of maximum resultset width. To avoid\nthis problem, use CHAR(n) whenever you're using COLUMN_GET in the select list.\n\nSee Dynamic Columns:Datatypes for more information about datatypes.\n\nURL: https://mariadb.com/kb/en/column_get/ [COLUMN_JSON] declaration=dyncol_blob category=Dynamic Column Functions -description=Returns a JSON representation of data in dyncol_blob. Can\nalso be used to display nested columns. See dynamic columns\nfor more information.\n \nExample\n \nselect item_name, COLUMN_JSON(dynamic_cols) from assets;\n+-----------------+----------------------------------------+\n| item_name | COLUMN_JSON(dynamic_cols) |\n+-----------------+----------------------------------------+\n| MariaDB T-shirt | {"size":"XL","color":"blue"} |\n| Thinkpad Laptop | {"color":"black","warranty":"3\nyears"} |\n+-----------------+----------------------------------------+\n \nLimitation: COLUMN_JSON will decode nested dynamic columns\nat a nesting level of not more than 10 levels deep. Dynamic\ncolumns that are nested deeper than 10 levels will be shown\nas BINARY string, without encoding. +description=Returns a JSON representation of data in dyncol_blob. Can also be used to\ndisplay nested columns. See dynamic columns for more information.\n\nExample\n-------\n\nselect item_name, COLUMN_JSON(dynamic_cols) from assets;\n+-----------------+----------------------------------------+\n| item_name | COLUMN_JSON(dynamic_cols) |\n+-----------------+----------------------------------------+\n| MariaDB T-shirt | {"size":"XL","color":"blue"} |\n| Thinkpad Laptop | {"color":"black","warranty":"3 years"} |\n+-----------------+----------------------------------------+\n\nLimitation: COLUMN_JSON will decode nested dynamic columns at a nesting level\nof not more than 10 levels deep. Dynamic columns that are nested deeper than\n10 levels will be shown as BINARY string, without encoding.\n\nURL: https://mariadb.com/kb/en/column_json/ [COLUMN_LIST] declaration=dyncol_blob category=Dynamic Column Functions -description=Since MariaDB 10.0.1, this function returns a\ncomma-separated list of column names. The names are quoted\nwith backticks.\n \nBefore MariaDB 10.0.1, it returned a comma-separated list of\ncolumn numbers, not names.\n \nSee dynamic columns for more information. +description=Returns a comma-separated list of column names. The names are quoted with\nbackticks.\n\nSee dynamic columns for more information.\n\nURL: https://mariadb.com/kb/en/column_list/ +[COMMIT] +declaration=the keyword WORK is simply noise and can be omitted without changing the effect +category=Transactions +description=The optional AND CHAIN clause is a convenience for initiating a new\ntransaction as soon as the old transaction terminates. If AND CHAIN is\nspecified, then there is effectively nothing between the old and new\ntransactions, although they remain separate. The characteristics of the new\ntransaction will be the same as the characteristics of the old one - that is,\nthe new transaction will have the same access mode, isolation level and\ndiagnostics area size (we'll discuss all of these shortly) as the transaction\njust terminated.\n\nRELEASE tells the server to disconnect the client immediately after the\ncurrent transaction.\n\nThere are NO RELEASE and AND NO CHAIN options. By default, commits do not\nRELEASE or CHAIN, but it's possible to change this default behavior with the\ncompletion_type server system variable. In this case, the AND NO CHAIN and NO\nRELEASE options override the server default.\n\nURL: https://mariadb.com/kb/en/commit/ [COMPRESS] declaration=string_to_compress category=Encryption Functions -description=Compresses a string and returns the result as a binary\nstring. This\nfunction requires MariaDB to have been compiled with a\ncompression\nlibrary such as zlib. Otherwise, the return value is always\nNULL. The\ncompressed string can be uncompressed with UNCOMPRESS().\n \nThe have_compress server system variable indicates whether a\ncompression library is present. \n \n\nSELECT LENGTH(COMPRESS(REPEAT(''a'',1000)));\n+------------------------------------+\n| LENGTH(COMPRESS(REPEAT(''a'',1000))) |\n+------------------------------------+\n| 21 |\n+------------------------------------+\n \nSELECT LENGTH(COMPRESS(''''));\n+----------------------+\n| LENGTH(COMPRESS('''')) |\n+----------------------+\n| 0 |\n+----------------------+\n \nSELECT LENGTH(COMPRESS(''a''));\n+-----------------------+\n| LENGTH(COMPRESS(''a'')) |\n+-----------------------+\n| 13 |\n+-----------------------+\n \nSELECT LENGTH(COMPRESS(REPEAT(''a'',16)));\n+----------------------------------+\n| LENGTH(COMPRESS(REPEAT(''a'',16))) |\n+----------------------------------+\n| 15 |\n+----------------------------------+ +description=Compresses a string and returns the result as a binary string. This function\nrequires MariaDB to have been compiled with a compression library such as\nzlib. Otherwise, the return value is always NULL. The compressed string can be\nuncompressed with UNCOMPRESS().\n\nThe have_compress server system variable indicates whether a compression\nlibrary is present.\n\nExamples\n--------\n\nSELECT LENGTH(COMPRESS(REPEAT('a',1000)));\n+------------------------------------+\n| LENGTH(COMPRESS(REPEAT('a',1000))) |\n+------------------------------------+\n| 21 |\n+------------------------------------+\n\nSELECT LENGTH(COMPRESS(''));\n+----------------------+\n| LENGTH(COMPRESS('')) |\n+----------------------+\n| 0 |\n+----------------------+\n\nSELECT LENGTH(COMPRESS('a'));\n+-----------------------+\n| LENGTH(COMPRESS('a')) |\n+-----------------------+\n| 13 |\n+-----------------------+\n\nSELECT LENGTH(COMPRESS(REPEAT('a',16)));\n+----------------------------------+\n| LENGTH(COMPRESS(REPEAT('a',16))) |\n+----------------------------------+\n| 15 |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/compress/ [CONCAT] declaration=str1,str2,... category=String Functions -description=Returns the string that results from concatenating the\narguments. May have one or more arguments. If all arguments\nare non-binary strings, the result is a non-binary string.\nIf the arguments include any binary strings, the result is a\nbinary string. A numeric argument is converted to its\nequivalent binary string form; if you want to avoid that,\nyou can use an explicit type cast, as in this example:\n \nSELECT CONCAT(CAST(int_col AS CHAR), char_col);\n \nCONCAT() returns NULL if any argument is NULL.\n \nA NULL parameter hides all information contained in other\nparameters from the result. Sometimes this is not desirable;\nto avoid this, you can:\nUse the CONCAT_WS() function with an empty separator,\nbecause that function is NULL-safe.\nUse IFNULL() to turn NULLs into empty strings.\n \nOracle Mode\n \nIn Oracle mode from MariaDB 10.3, CONCAT ignores NULL.\n \n\nSELECT CONCAT(''Ma'', ''ria'', ''DB'');\n+---------------------------+\n| CONCAT(''Ma'', ''ria'', ''DB'') |\n+---------------------------+\n| MariaDB |\n+---------------------------+\n \nSELECT CONCAT(''Ma'', ''ria'', NULL, ''DB'');\n+---------------------------------+\n| CONCAT(''Ma'', ''ria'', NULL, ''DB'') |\n+---------------------------------+\n| NULL |\n+---------------------------------+\n \nSELECT CONCAT(42.0);\n+--------------+\n| CONCAT(42.0) |\n+--------------+\n| 42.0 |\n+--------------+\n \nUsing IFNULL() to handle NULLs:\n \nSELECT CONCAT(''The value of @v is: '', IFNULL(@v, ''''));\n+------------------------------------------------+\n| CONCAT(''The value of @v is: '', IFNULL(@v, '''')) |\n+------------------------------------------------+\n| The value of @v is: |\n+------------------------------------------------+\n \nIn Oracle mode, from MariaDB 10.3:\n \nSELECT CONCAT(''Ma'', ''ria'', NULL, ''DB'');\n+---------------------------------+\n| CONCAT(''Ma'', ''ria'', NULL, ''DB'') |\n+---------------------------------+\n| MariaDB |\n+---------------------------------+ +description=Returns the string that results from concatenating the arguments. May have one\nor more arguments. If all arguments are non-binary strings, the result is a\nnon-binary string. If the arguments include any binary strings, the result is\na binary string. A numeric argument is converted to its equivalent binary\nstring form; if you want to avoid that, you can use an explicit type cast, as\nin this example:\n\nSELECT CONCAT(CAST(int_col AS CHAR), char_col);\n\nCONCAT() returns NULL if any argument is NULL.\n\nA NULL parameter hides all information contained in other parameters from the\nresult. Sometimes this is not desirable; to avoid this, you can:\n\n* Use the CONCAT_WS() function with an empty separator, because that function\nis NULL-safe.\n* Use IFNULL() to turn NULLs into empty strings.\n\nOracle Mode\n-----------\n\nIn Oracle mode, CONCAT ignores NULL.\n\nExamples\n--------\n\nSELECT CONCAT('Ma', 'ria', 'DB');\n+---------------------------+\n| CONCAT('Ma', 'ria', 'DB') |\n+---------------------------+\n| MariaDB |\n+---------------------------+\n\nSELECT CONCAT('Ma', 'ria', NULL, 'DB');\n+---------------------------------+\n| CONCAT('Ma', 'ria', NULL, 'DB') |\n+---------------------------------+\n| NULL |\n+---------------------------------+\n\nSELECT CONCAT(42.0);\n+--------------+\n| CONCAT(42.0) |\n+--------------+\n| 42.0 |\n+--------------+\n\nUsing IFNULL() to handle NULLs:\n\nSELECT CONCAT('The value of @v is: ', IFNULL(@v, ''));\n ... [CONCAT_WS] declaration=separator,str1,str2,... category=String Functions -description=CONCAT_WS() stands for Concatenate With Separator and is a\nspecial form of CONCAT(). The first argument is the\nseparator for the rest of the arguments. The separator is\nadded between the strings to be concatenated. The separator\ncan be a string, as can the rest of the arguments.\n \nIf the separator is NULL, the result is NULL; all other NULL\nvalues are skipped. This makes CONCAT_WS() suitable when you\nwant to concatenate some values and avoid losing all\ninformation if one of them is NULL.\n \n\nSELECT CONCAT_WS('','',''First name'',''Second name'',''Last\nName'');\n+-------------------------------------------------------+\n| CONCAT_WS('','',''First name'',''Second name'',''Last\nName'') |\n+-------------------------------------------------------+\n| First name,Second name,Last Name |\n+-------------------------------------------------------+\n \nSELECT CONCAT_WS(''-'',''Floor'',NULL,''Room'');\n+------------------------------------+\n| CONCAT_WS(''-'',''Floor'',NULL,''Room'') |\n+------------------------------------+\n| Floor-Room |\n+------------------------------------+\n \nIn some cases, remember to include a space in the separator\nstring:\n \nSET @a = ''gnu'', @b = ''penguin'', @c = ''sea lion'';\n \nQuery OK, 0 rows affected (0.00 sec)\n \nSELECT CONCAT_WS('', '', @a, @b, @c);\n+-----------------------------+\n| CONCAT_WS('', '', @a, @b, @c) |\n+-----------------------------+\n| gnu, penguin, sea lion |\n+-----------------------------+\n \nUsing CONCAT_WS() to handle NULLs:\n \nSET @a = ''a'', @b = NULL, @c = ''c'';\n \nSELECT CONCAT_WS('''', @a, @b, @c);\n+---------------------------+\n| CONCAT_WS('''', @a, @b, @c) |\n+---------------------------+\n| ac |\n+---------------------------+ +description=CONCAT_WS() stands for Concatenate With Separator and is a special form of\nCONCAT(). The first argument is the separator for the rest of the arguments.\nThe separator is added between the strings to be concatenated. The separator\ncan be a string, as can the rest of the arguments.\n\nIf the separator is NULL, the result is NULL; all other NULL values are\nskipped. This makes CONCAT_WS() suitable when you want to concatenate some\nvalues and avoid losing all information if one of them is NULL.\n\nExamples\n--------\n\nSELECT CONCAT_WS(',','First name','Second name','Last Name');\n+-------------------------------------------------------+\n| CONCAT_WS(',','First name','Second name','Last Name') |\n+-------------------------------------------------------+\n| First name,Second name,Last Name |\n+-------------------------------------------------------+\n\nSELECT CONCAT_WS('-','Floor',NULL,'Room');\n+------------------------------------+\n| CONCAT_WS('-','Floor',NULL,'Room') |\n+------------------------------------+\n| Floor-Room |\n+------------------------------------+\n\nIn some cases, remember to include a space in the separator string:\n\nSET @a = 'gnu', @b = 'penguin', @c = 'sea lion';\nQuery OK, 0 rows affected (0.00 sec)\n\nSELECT CONCAT_WS(', ', @a, @b, @c);\n+-----------------------------+\n| CONCAT_WS(', ', @a, @b, @c) |\n+-----------------------------+\n| gnu, penguin, sea lion |\n+-----------------------------+\n\nUsing CONCAT_WS() to handle NULLs:\n\nSET @a = 'a', @b = NULL, @c = 'c';\n\nSELECT CONCAT_WS('', @a, @b, @c);\n+---------------------------+\n| CONCAT_WS('', @a, @b, @c) |\n+---------------------------+\n| ac |\n+---------------------------+\n\nURL: https://mariadb.com/kb/en/concat_ws/ [CONNECTION_ID] declaration= category=Information Functions -description=Returns the connection ID (thread ID) for the connection.\nEvery\nthread (including events) has an ID that is unique among the\nset of currently\nconnected clients.\n \nUntil MariaDB 10.3.1, returns MYSQL_TYPE_LONGLONG, or\nbigint(10), in all cases. From MariaDB 10.3.1, returns\nMYSQL_TYPE_LONG, or int(10), when the result would fit\nwithin 32-bits.\n \n\nSELECT CONNECTION_ID();\n+-----------------+\n| CONNECTION_ID() |\n+-----------------+\n| 3 |\n+-----------------+ +description=Returns the connection ID for the connection. Every connection (including\nevents) has an ID that is unique among the set of currently connected clients.\n\nUntil MariaDB 10.3.1, returns MYSQL_TYPE_LONGLONG, or bigint(10), in all\ncases. From MariaDB 10.3.1, returns MYSQL_TYPE_LONG, or int(10), when the\nresult would fit within 32-bits.\n\nExamples\n--------\n\nSELECT CONNECTION_ID();\n+-----------------+\n| CONNECTION_ID() |\n+-----------------+\n| 3 |\n+-----------------+\n\nURL: https://mariadb.com/kb/en/connection_id/ [CONTAINS] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether a geometry g1 completely\ncontains geometry g2. CONTAINS() is based on the original\nMySQL implementation and uses object bounding rectangles,\nwhile ST_CONTAINS() uses object shapes. \n \nThis tests the opposite relationship to Within(). -[CONVERT1] -name=CONVERT +description=Returns 1 or 0 to indicate whether a geometry g1 completely contains geometry\ng2. CONTAINS() is based on the original MySQL implementation and uses object\nbounding rectangles, while ST_CONTAINS() uses object shapes.\n\nThis tests the opposite relationship to Within().\n\nURL: https://mariadb.com/kb/en/contains/ +[CONV] +declaration=N,from_base,to_base +category=Numeric Functions +description=Converts numbers between different number bases. Returns a string\nrepresentation of the number N, converted from base from_base to base to_base.\n\nReturns NULL if any argument is NULL, or if the second or third argument are\nnot in the allowed range.\n\nThe argument N is interpreted as an integer, but may be specified as an\ninteger or a string. The minimum base is 2 and the maximum base is 36 (prior\nto MariaDB 11.4.0) or 62 (from MariaDB 11.4.0). If to_base is a negative\nnumber, N is regarded as a signed number. Otherwise, N is treated as unsigned.\nCONV() works with 64-bit precision.\n\nSome shortcuts for this function are also available: BIN(), OCT(), HEX(),\nUNHEX(). Also, MariaDB allows binary literal values and hexadecimal literal\nvalues.\n\nExamples\n--------\n\nSELECT CONV('a',16,2);\n+----------------+\n| CONV('a',16,2) |\n+----------------+\n| 1010 |\n+----------------+\n\nSELECT CONV('6E',18,8);\n+-----------------+\n| CONV('6E',18,8) |\n+-----------------+\n| 172 |\n+-----------------+\n\nSELECT CONV(-17,10,-18);\n+------------------+\n| CONV(-17,10,-18) |\n+------------------+\n| -H |\n+------------------+\n\nSELECT CONV(12+'10'+'10'+0xa,10,10);\n+------------------------------+\n| CONV(12+'10'+'10'+0xa,10,10) |\n+------------------------------+\n| 42 |\n+------------------------------+\n\nURL: https://mariadb.com/kb/en/conv/ +[CONVERT] declaration=expr,type category=String Functions -description=The CONVERT() and CAST() functions take a value of one type\nand produce a value of another type.\n \nThe type can be one of the following values:\nBINARY\nCHAR\nDATE\nDATETIME \nDECIMAL[(M[,D])]\nDOUBLE \nFLOAT From MariaDB 10.4.5\nINTEGER \nShort for SIGNED INTEGER\n \nSIGNED [INTEGER]\nTIME \nUNSIGNED [INTEGER]\n \nNote that in MariaDB, INT and INTEGER are the same thing.\n \nBINARY produces a string with the BINARY data type. If the\noptional length is given, BINARY(N) causes the cast to use\nno more than N bytes of the argument. Values shorter than\nthe given number in bytes are padded with 0x00 bytes to make\nthem equal the length value.\n \nCHAR(N) causes the cast to use no more than the number of\ncharacters given in the argument.\n \nThe main difference between the CAST() and CONVERT() is that\nCONVERT(expr,type) is ODBC syntax while CAST(expr as type)\nand CONVERT(... USING ...) are SQL92 syntax.\n \nCONVERT() with USING is used to convert data between\ndifferent character sets. In MariaDB, transcoding names are\nthe same as the\ncorresponding character set names. For example, this\nstatement\nconverts the string ''abc'' in the default character set to\nthe\ncorresponding string in the utf8 character set:\n \nSELECT CONVERT(''abc'' USING utf8);\n \n\nSELECT enum_col FROM tbl_name \nORDER BY CAST(enum_col AS CHAR);\n \nConverting a BINARY to string to permit the LOWER function\nto work:\n \nSET @x = ''AardVark'';\n \nSET @x = BINARY ''AardVark'';\n \nSELECT LOWER(@x), LOWER(CONVERT (@x USING latin1));\n+-----------+----------------------------------+\n| LOWER(@x) | LOWER(CONVERT (@x USING latin1)) |\n+-----------+----------------------------------+\n| AardVark | aardvark |\n+-----------+----------------------------------+ -[CONVERT2] -name=CONVERT -declaration=expr USING transcoding_name -category=String Functions -description=The CONVERT() and CAST() functions take a value of one type\nand produce a value of another type.\n \nThe type can be one of the following values:\nBINARY\nCHAR\nDATE\nDATETIME \nDECIMAL[(M[,D])]\nDOUBLE \nFLOAT From MariaDB 10.4.5\nINTEGER \nShort for SIGNED INTEGER\n \nSIGNED [INTEGER]\nTIME \nUNSIGNED [INTEGER]\n \nNote that in MariaDB, INT and INTEGER are the same thing.\n \nBINARY produces a string with the BINARY data type. If the\noptional length is given, BINARY(N) causes the cast to use\nno more than N bytes of the argument. Values shorter than\nthe given number in bytes are padded with 0x00 bytes to make\nthem equal the length value.\n \nCHAR(N) causes the cast to use no more than the number of\ncharacters given in the argument.\n \nThe main difference between the CAST() and CONVERT() is that\nCONVERT(expr,type) is ODBC syntax while CAST(expr as type)\nand CONVERT(... USING ...) are SQL92 syntax.\n \nCONVERT() with USING is used to convert data between\ndifferent character sets. In MariaDB, transcoding names are\nthe same as the\ncorresponding character set names. For example, this\nstatement\nconverts the string ''abc'' in the default character set to\nthe\ncorresponding string in the utf8 character set:\n \nSELECT CONVERT(''abc'' USING utf8);\n \n\nSELECT enum_col FROM tbl_name \nORDER BY CAST(enum_col AS CHAR);\n \nConverting a BINARY to string to permit the LOWER function\nto work:\n \nSET @x = ''AardVark'';\n \nSET @x = BINARY ''AardVark'';\n \nSELECT LOWER(@x), LOWER(CONVERT (@x USING latin1));\n+-----------+----------------------------------+\n| LOWER(@x) | LOWER(CONVERT (@x USING latin1)) |\n+-----------+----------------------------------+\n| AardVark | aardvark |\n+-----------+----------------------------------+ +description=The CONVERT() and CAST() functions take a value of one type and produce a\nvalue of another type.\n\nThe type can be one of the following values:\n\n* BINARY\n* CHAR\n* DATE\n* DATETIME\n* DECIMAL[(M[,D])]\n* DOUBLE\n* FLOAT (from MariaDB 10.4.5)\n* INTEGER\nShort for SIGNED INTEGER\n\n* SIGNED [INTEGER]\n* UNSIGNED [INTEGER]\n* TIME\n* VARCHAR (in Oracle mode, from MariaDB 10.3)\n\nNote that in MariaDB, INT and INTEGER are the same thing.\n\nBINARY produces a string with the BINARY data type. If the optional length is\ngiven, BINARY(N) causes the cast to use no more than N bytes of the argument.\nValues shorter than the given number in bytes are padded with 0x00 bytes to\nmake them equal the length value.\n\nCHAR(N) causes the cast to use no more than the number of characters given in\nthe argument.\n\nThe main difference between the CAST() and CONVERT() is that\nCONVERT(expr,type) is ODBC syntax while CAST(expr as type) and CONVERT(...\nUSING ...) are SQL92 syntax.\n\nCONVERT() with USING is used to convert data between different character sets.\nIn MariaDB, transcoding names are the same as the corresponding character set\nnames. For example, this statement converts the string 'abc' in the default\ncharacter set to the corresponding string in the utf8 character set:\n\nSELECT CONVERT('abc' USING utf8);\n\nExamples\n--------\n\nSELECT enum_col FROM tbl_name \nORDER BY CAST(enum_col AS CHAR);\n\nConverting a BINARY to string to permit the LOWER function to work:\n\nSET @x = 'AardVark';\n ... [CONVERT_TZ] declaration=dt,from_tz,to_tz category=Date and Time Functions -description=CONVERT_TZ() converts a datetime value dt from the time zone\ngiven by from_tz to the time zone given by to_tz and returns\nthe resulting value.\n \nIn order to use named time zones, such as GMT, MET or\nAfrica/Johannesburg, the time_zone tables must be loaded\n(see mysql_tzinfo_to_sql).\n \nNo conversion will take place if the value falls outside of\nthe supported TIMESTAMP range (''1970-01-01 00:00:01'' to\n''2038-01-19 05:14:07'' UTC) when converted from from_tz to\nUTC.\n \nThis function returns NULL if the arguments are invalid (or\nnamed time zones have not been loaded).\n \nSee time zones for more information.\n \n\nSELECT CONVERT_TZ(''2016-01-01\n12:00:00'',''+00:00'',''+10:00'');\n+-----------------------------------------------------+\n| CONVERT_TZ(''2016-01-01 12:00:00'',''+00:00'',''+10:00'')\n|\n+-----------------------------------------------------+\n| 2016-01-01 22:00:00 |\n+-----------------------------------------------------+\n \nUsing named time zones (with the time zone tables loaded):\n \nSELECT CONVERT_TZ(''2016-01-01\n12:00:00'',''GMT'',''Africa/Johannesburg'');\n+---------------------------------------------------------------+\n| CONVERT_TZ(''2016-01-01\n12:00:00'',''GMT'',''Africa/Johannesburg'') |\n+---------------------------------------------------------------+\n| 2016-01-01 14:00:00 |\n+---------------------------------------------------------------+\n \nThe value is out of the TIMESTAMP range, so no conversion\ntakes place:\n \nSELECT CONVERT_TZ(''1969-12-31\n22:00:00'',''+00:00'',''+10:00'');\n+-----------------------------------------------------+\n| CONVERT_TZ(''1969-12-31 22:00:00'',''+00:00'',''+10:00'')\n|\n+-----------------------------------------------------+\n| 1969-12-31 22:00:00 |\n+-----------------------------------------------------+ -[CONV] -declaration=N,from_base,to_base -category=Numeric Functions -description=Converts numbers between different number bases. Returns a\nstring\nrepresentation of the number N, converted from base\nfrom_base\nto base to_base.\n \nReturns NULL if any argument is NULL, or if the second or\nthird argument are not in the allowed range.\n \nThe argument N is interpreted as an integer, but may be\nspecified as an\ninteger or a string. The minimum base is 2 and the maximum\nbase is 36. If\nto_base is a negative number, N is regarded as a signed\nnumber.\nOtherwise, N is treated as unsigned. CONV() works with\n64-bit\nprecision.\n \nSome shortcuts for this function are also available: BIN(),\nOCT(), HEX(), UNHEX(). Also, MariaDB allows binary literal\nvalues and hexadecimal literal values.\n \n\nSELECT CONV(''a'',16,2);\n+----------------+\n| CONV(''a'',16,2) |\n+----------------+\n| 1010 |\n+----------------+\n \nSELECT CONV(''6E'',18,8);\n+-----------------+\n| CONV(''6E'',18,8) |\n+-----------------+\n| 172 |\n+-----------------+\n \nSELECT CONV(-17,10,-18);\n+------------------+\n| CONV(-17,10,-18) |\n+------------------+\n| -H |\n+------------------+\n \nSELECT CONV(12+''10''+''10''+0xa,10,10);\n+------------------------------+\n| CONV(12+''10''+''10''+0xa,10,10) |\n+------------------------------+\n| 42 |\n+------------------------------+ +description=CONVERT_TZ() converts a datetime value dt from the time zone given by from_tz\nto the time zone given by to_tz and returns the resulting value.\n\nIn order to use named time zones, such as GMT, MET or Africa/Johannesburg, the\ntime_zone tables must be loaded (see mysql_tzinfo_to_sql).\n\nNo conversion will take place if the value falls outside of the supported\nTIMESTAMP range ('1970-01-01 00:00:01' to '2038-01-19 05:14:07' UTC) when\nconverted from from_tz to UTC.\n\nThis function returns NULL if the arguments are invalid (or named time zones\nhave not been loaded).\n\nSee time zones for more information.\n\nExamples\n--------\n\nSELECT CONVERT_TZ('2016-01-01 12:00:00','+00:00','+10:00');\n+-----------------------------------------------------+\n| CONVERT_TZ('2016-01-01 12:00:00','+00:00','+10:00') |\n+-----------------------------------------------------+\n| 2016-01-01 22:00:00 |\n+-----------------------------------------------------+\n\nUsing named time zones (with the time zone tables loaded):\n\nSELECT CONVERT_TZ('2016-01-01 12:00:00','GMT','Africa/Johannesburg');\n+---------------------------------------------------------------+\n| CONVERT_TZ('2016-01-01 12:00:00','GMT','Africa/Johannesburg') |\n+---------------------------------------------------------------+\n| 2016-01-01 14:00:00 |\n+---------------------------------------------------------------+\n\nThe value is out of the TIMESTAMP range, so no conversion takes place:\n\nSELECT CONVERT_TZ('1969-12-31 22:00:00','+00:00','+10:00');\n+-----------------------------------------------------+\n| CONVERT_TZ('1969-12-31 22:00:00','+00:00','+10:00') |\n+-----------------------------------------------------+\n| 1969-12-31 22:00:00 |\n+-----------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/convert_tz/ [COS] declaration=X category=Numeric Functions -description=Returns the cosine of X, where X is given in radians.\n \n\nSELECT COS(PI());\n+-----------+\n| COS(PI()) |\n+-----------+\n| -1 |\n+----------- +description=Returns the cosine of X, where X is given in radians.\n\nExamples\n--------\n\nSELECT COS(PI());\n+-----------+\n| COS(PI()) |\n+-----------+\n| -1 |\n+-----------+\n\nURL: https://mariadb.com/kb/en/cos/ [COT] declaration=X category=Numeric Functions -description=Returns the cotangent of X.\n \n\nSELECT COT(42);\n+--------------------+\n| COT(42) |\n+--------------------+\n| 0.4364167060752729 |\n+--------------------+\n \nSELECT COT(12);\n+---------------------+\n| COT(12) |\n+---------------------+\n| -1.5726734063976893 |\n+---------------------+\n \nSELECT COT(0);\nERROR 1690 (22003): DOUBLE value is out of range in\n''cot(0)'' +description=Returns the cotangent of X.\n\nExamples\n--------\n\nSELECT COT(42);\n+--------------------+\n| COT(42) |\n+--------------------+\n| 0.4364167060752729 |\n+--------------------+\n\nSELECT COT(12);\n+---------------------+\n| COT(12) |\n+---------------------+\n| -1.5726734063976893 |\n+---------------------+\n\nSELECT COT(0);\nERROR 1690 (22003): DOUBLE value is out of range in 'cot(0)'\n\nURL: https://mariadb.com/kb/en/cot/ [COUNT] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=Returns a count of the number of non-NULL values of expr in\nthe rows retrieved by a SELECT statement. The result is a\nBIGINT value. It is an aggregate function, and so can be\nused with the GROUP BY clause.\n \nCOUNT(*) counts the total number of rows in a table.\n \nCOUNT() returns 0 if there were no matching rows.\n \nFrom MariaDB 10.2.0, COUNT() can be used as a window\nfunction.\n \n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score\nTINYINT); \n \nINSERT INTO student VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87), (''Tatiana'', ''Tuning'', 83);\n \nSELECT COUNT(*) FROM student;\n \n+----------+\n| COUNT(*) |\n+----------+\n| 8 |\n+----------+\n \nCOUNT(DISTINCT) example:\n \nSELECT COUNT(DISTINCT (name)) FROM student;\n \n+------------------------+\n| COUNT(DISTINCT (name)) |\n+------------------------+\n| 4 |\n+------------------------+\n \nAs a window function\n \nCREATE OR REPLACE TABLE student_test (name CHAR(10), test\nCHAR(10), score TINYINT);\n \nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87);\n \nSELECT name, test, score, COUNT(score) OVER (PARTITION BY\nname) \n AS tests_written FROM student_test;\n \n+---------+--------+-------+---------------+\n| name | test | score | tests_written |\n+---------+--------+-------+---------------+\n| Chun | SQL | 75 | 2 |\n| Chun | Tuning | 73 | 2 |\n| Esben | SQL | 43 | 2 |\n| Esben | Tuning | 31 | 2 |\n| Kaolin | SQL | 56 | 2 |\n| Kaolin | Tuning | 88 | 2 |\n| Tatiana | SQL | 87 | 1 |\n+---------+--------+-------+---------------+ +description=Returns a count of the number of non-NULL values of expr in the rows retrieved\nby a SELECT statement. The result is a BIGINT value. It is an aggregate\nfunction, and so can be used with the GROUP BY clause.\n\nCOUNT(*) counts the total number of rows in a table.\n\nCOUNT() returns 0 if there were no matching rows.\n\nCOUNT() can be used as a window function.\n\nExamples\n--------\n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT);\n\nINSERT INTO student VALUES \n ('Chun', 'SQL', 75), ('Chun', 'Tuning', 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87), ('Tatiana', 'Tuning', 83);\n\nSELECT COUNT(*) FROM student;\n+----------+\n| COUNT(*) |\n+----------+\n| 8 |\n+----------+\n\nCOUNT(DISTINCT) example:\n\nSELECT COUNT(DISTINCT (name)) FROM student;\n+------------------------+\n| COUNT(DISTINCT (name)) |\n+------------------------+\n| 4 |\n+------------------------+\n\nAs a window function\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n ('Chun', 'SQL', 75), ('Chun', 'Tuning', 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87);\n\nSELECT name, test, score, COUNT(score) OVER (PARTITION BY name) \n AS tests_written FROM student_test;\n ... [CRC32] declaration=expr category=Numeric Functions -description=Computes a cyclic redundancy check value and returns a\n32-bit unsigned\nvalue. The result is NULL if the argument is NULL. The\nargument is\nexpected to be a string and (if possible) is treated as one\nif it is\nnot.\n \n\nSELECT CRC32(''MariaDB'');\n+------------------+\n| CRC32(''MariaDB'') |\n+------------------+\n| 4227209140 |\n+------------------+\n \nSELECT CRC32(''mariadb'');\n+------------------+\n| CRC32(''mariadb'') |\n+------------------+\n| 2594253378 |\n+------------------+ +description=Computes a cyclic redundancy check (CRC) value and returns a 32-bit unsigned\nvalue. The result is NULL if the argument is NULL. The argument is expected to\nbe a string and (if possible) is treated as one if it is not.\n\nUses the ISO 3309 polynomial that used by zlib and many others. MariaDB 10.8\nintroduced the CRC32C() function, which uses the alternate Castagnoli\npolynomia.\n\nMariaDB starting with 10.8\n--------------------------\nOften, CRC is computed in pieces. To facilitate this, MariaDB 10.8.0\nintroduced an optional parameter: CRC32('MariaDB')=CRC32(CRC32('Maria'),'DB').\n\nExamples\n--------\n\nSELECT CRC32('MariaDB');\n+------------------+\n| CRC32('MariaDB') |\n+------------------+\n| 4227209140 |\n+------------------+\n\nSELECT CRC32('mariadb');\n+------------------+\n| CRC32('mariadb') |\n+------------------+\n| 2594253378 |\n+------------------+\n\nFrom MariaDB 10.8.0\n\nSELECT CRC32(CRC32('Maria'),'DB');\n+----------------------------+\n| CRC32(CRC32('Maria'),'DB') |\n+----------------------------+\n| 4227209140 |\n+----------------------------+\n\nURL: https://mariadb.com/kb/en/crc32/ +[CRC32C] +declaration=[par,]expr +category=Numeric Functions +description=MariaDB has always included a native unary function CRC32() that computes the\nCRC-32 of a string using the ISO 3309 polynomial that used by zlib and many\nothers.\n\nInnoDB and MyRocks use a different polynomial, which was implemented in SSE4.2\ninstructions that were introduced in the Intel Nehalem microarchitecture. This\nis commonly called CRC-32C (Castagnoli).\n\nThe CRC32C function uses the Castagnoli polynomial.\n\nThis allows SELECT…INTO DUMPFILE to be used for the creation of files with\nvalid checksums, such as a logically empty InnoDB redo log file ib_logfile0\ncorresponding to a particular log sequence number.\n\nThe optional parameter allows the checksum to be computed in pieces:\nCRC32C('MariaDB')=CRC32C(CRC32C('Maria'),'DB').\n\nExamples\n--------\n\nSELECT CRC32C('MariaDB');\n+-------------------+\n| CRC32C('MariaDB') |\n+-------------------+\n| 809606978 |\n+-------------------+\n\nSELECT CRC32C(CRC32C('Maria'),'DB');\n+------------------------------+\n| CRC32C(CRC32C('Maria'),'DB') |\n+------------------------------+\n| 809606978 |\n+------------------------------+\n\nURL: https://mariadb.com/kb/en/crc32c/ [CROSSES] declaration=g1,g2 category=Geometry Relations -description=Returns 1 if g1 spatially crosses g2. Returns NULL if g1 is\na Polygon or a MultiPolygon, or if g2 is a\nPoint or a MultiPoint. Otherwise, returns 0.\n \nThe term spatially crosses denotes a spatial relation\nbetween two\ngiven geometries that has the following properties:\nThe two geometries intersect\nTheir intersection results in a geometry that has a\ndimension that is one\n less than the maximum dimension of the two given geometries\nTheir intersection is not equal to either of the two given\ngeometries\n \nCROSSES() is based on the original MySQL implementation, and\nuses object bounding rectangles, while ST_CROSSES() uses\nobject shapes. -[CUME_DIST1] -name=CUME_DIST +description=Returns 1 if g1 spatially crosses g2. Returns NULL if g1 is a Polygon or a\nMultiPolygon, or if g2 is a Point or a MultiPoint. Otherwise, returns 0.\n\nThe term spatially crosses denotes a spatial relation between two given\ngeometries that has the following properties:\n\n* The two geometries intersect\n* Their intersection results in a geometry that has a dimension that is one\n less than the maximum dimension of the two given geometries\n* Their intersection is not equal to either of the two given geometries\n\nCROSSES() is based on the original MySQL implementation, and uses object\nbounding rectangles, while ST_CROSSES() uses object shapes.\n\nURL: https://mariadb.com/kb/en/crosses/ +[CUME_DIST] declaration= category=Window Functions -description=CUME_DIST() is a window function that returns the cumulative\ndistribution of a given row. The following formula is used\nto calculate the value:\n \n(number of rows -[CUME_DIST2] -name=CUME_DIST -declaration=[ PARTITION BY partition_expression ] [ ORDER BY order_list ] -category=Window Functions -description=CUME_DIST() is a window function that returns the cumulative\ndistribution of a given row. The following formula is used\nto calculate the value:\n \n(number of rows -[CURRENT_TIMESTAMP] -name=CURRENT_TIMESTAMP +description=CUME_DIST() is a window function that returns the cumulative distribution of a\ngiven row. The following formula is used to calculate the value:\n\n(number of rows <= current row) / (total rows)\n\nExamples\n--------\n\ncreate table t1 (\n pk int primary key,\n a int,\n b int\n);\n\ninsert into t1 values\n( 1 , 0, 10),\n( 2 , 0, 10),\n( 3 , 1, 10),\n( 4 , 1, 10),\n( 8 , 2, 10),\n( 5 , 2, 20),\n( 6 , 2, 20),\n( 7 , 2, 20),\n( 9 , 4, 20),\n(10 , 4, 20);\n\nselect pk, a, b,\n rank() over (order by a) as rank,\n percent_rank() over (order by a) as pct_rank,\n cume_dist() over (order by a) as cume_dist\nfrom t1;\n+----+------+------+------+--------------+--------------+\n| pk | a | b | rank | pct_rank | cume_dist |\n+----+------+------+------+--------------+--------------+\n| 1 | 0 | 10 | 1 | 0.0000000000 | 0.2000000000 |\n| 2 | 0 | 10 | 1 | 0.0000000000 | 0.2000000000 |\n| 3 | 1 | 10 | 3 | 0.2222222222 | 0.4000000000 |\n| 4 | 1 | 10 | 3 | 0.2222222222 | 0.4000000000 |\n| 5 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 6 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 7 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 8 | 2 | 10 | 5 | 0.4444444444 | 0.8000000000 |\n| 9 | 4 | 20 | 9 | 0.8888888889 | 1.0000000000 |\n| 10 | 4 | 20 | 9 | 0.8888888889 | 1.0000000000 |\n+----+------+------+------+--------------+--------------+\n\nselect pk, a, b,\n percent_rank() over (order by pk) as pct_rank,\n cume_dist() over (order by pk) as cume_dist\nfrom t1 order by pk;\n ... +[CURDATE] declaration= category=Date and Time Functions -description=CURRENT_TIMESTAMP and CURRENT_TIMESTAMP() are synonyms for NOW(). -[CURDATE] +description=CURDATE returns the current date as a value in 'YYYY-MM-DD' or YYYYMMDD\nformat, depending on whether the function is used in a string or numeric\ncontext.\n\nCURRENT_DATE and CURRENT_DATE() are synonyms.\n\nExamples\n--------\n\nSELECT CURDATE();\n+------------+\n| CURDATE() |\n+------------+\n| 2019-03-05 |\n+------------+\n\nIn a numeric context (note this is not performing date calculations):\n\nSELECT CURDATE() +0;\n+--------------+\n| CURDATE() +0 |\n+--------------+\n| 20190305 |\n+--------------+\n\nData calculation:\n\nSELECT CURDATE() - INTERVAL 5 DAY;\n+----------------------------+\n| CURDATE() - INTERVAL 5 DAY |\n+----------------------------+\n| 2019-02-28 |\n+----------------------------+\n\nURL: https://mariadb.com/kb/en/curdate/ +[CURRENT_DATE] declaration= category=Date and Time Functions -description=Returns the current date as a value in ''YYYY-MM-DD'' or\nYYYYMMDD\nformat, depending on whether the function is used in a\nstring or\nnumeric context.\n \n\nSELECT CURDATE();\n+------------+\n| CURDATE() |\n+------------+\n| 2019-03-05 |\n+------------+\n \nIn a numeric context (note this is not performing date\ncalculations):\n \nSELECT CURDATE() +0;\n \n+--------------+\n| CURDATE() +0 |\n+--------------+\n| 20190305 |\n+--------------+\n \nData calculation:\n \nSELECT CURDATE() - INTERVAL 5 DAY;\n \n+----------------------------+\n| CURDATE() - INTERVAL 5 DAY |\n+----------------------------+\n| 2019-02-28 |\n+----------------------------+ +description=CURRENT_DATE and CURRENT_DATE() are synonyms for CURDATE().\n\nURL: https://mariadb.com/kb/en/current_date/ +[CURRENT_ROLE] +declaration= +category=Information Functions +description=Returns the current role name. This determines your access privileges. The\nreturn value is a string in the utf8 character set.\n\nIf there is no current role, NULL is returned.\n\nThe output of SELECT CURRENT_ROLE is equivalent to the contents of the\nENABLED_ROLES Information Schema table.\n\nUSER() returns the combination of user and host used to login. CURRENT_USER()\nreturns the account used to determine current connection's privileges.\n\nStatements using the CURRENT_ROLE function are not safe for statement-based\nreplication.\n\nExamples\n--------\n\nSELECT CURRENT_ROLE;\n+--------------+\n| CURRENT_ROLE |\n+--------------+\n| NULL |\n+--------------+\n\nSET ROLE staff;\n\nSELECT CURRENT_ROLE;\n+--------------+\n| CURRENT_ROLE |\n+--------------+\n| staff |\n+--------------+\n\nURL: https://mariadb.com/kb/en/current_role/ +[CURRENT_TIME] +declaration=[precision] +category=Date and Time Functions +description=CURRENT_TIME and CURRENT_TIME() are synonyms for CURTIME().\n\nURL: https://mariadb.com/kb/en/current_time/ +[CURRENT_TIMESTAMP] +declaration=[precision] +category=Date and Time Functions +description=CURRENT_TIMESTAMP and CURRENT_TIMESTAMP() are synonyms for NOW().\n\nURL: https://mariadb.com/kb/en/current_timestamp/ +[CURRENT_USER] +declaration= +category=Information Functions +description=Returns the user name and host name combination for the MariaDB account that\nthe server used to authenticate the current client. This account determines\nyour access privileges. The return value is a string in the utf8 character set.\n\nThe value of CURRENT_USER() can differ from the value of USER().\nCURRENT_ROLE() returns the current active role.\n\nStatements using the CURRENT_USER function are not safe for statement-based\nreplication.\n\nExamples\n--------\n\nshell> mysql --user="anonymous"\n\nselect user(),current_user();\n+---------------------+----------------+\n| user() | current_user() |\n+---------------------+----------------+\n| anonymous@localhost | @localhost |\n+---------------------+----------------+\n\nWhen calling CURRENT_USER() in a stored procedure, it returns the owner of the\nstored procedure, as defined with DEFINER.\n\nURL: https://mariadb.com/kb/en/current_user/ [CURTIME] declaration=[precision] category=Date and Time Functions -description=Returns the current time as a value in ''HH:MM:SS'' or\nHHMMSS.uuuuuu format, depending on whether the function is\nused in a string or numeric context. The value is expressed\nin the current time zone.\n \nThe optional precision determines the microsecond precision.\nSee Microseconds in MariaDB.\n \n\nSELECT CURTIME();\n+-----------+\n| CURTIME() |\n+-----------+\n| 12:45:39 |\n+-----------+\n \nSELECT CURTIME() + 0;\n \n+---------------+\n| CURTIME() + 0 |\n+---------------+\n| 124545.000000 |\n+---------------+\n \nWith precision:\n \nSELECT CURTIME(2);\n+-------------+\n| CURTIME(2) |\n+-------------+\n| 09:49:08.09 |\n+-------------+ +description=Returns the current time as a value in 'HH:MM:SS' or HHMMSS.uuuuuu format,\ndepending on whether the function is used in a string or numeric context. The\nvalue is expressed in the current time zone.\n\nThe optional precision determines the microsecond precision. See Microseconds\nin MariaDB.\n\nExamples\n--------\n\nSELECT CURTIME();\n+-----------+\n| CURTIME() |\n+-----------+\n| 12:45:39 |\n+-----------+\n\nSELECT CURTIME() + 0;\n+---------------+\n| CURTIME() + 0 |\n+---------------+\n| 124545.000000 |\n+---------------+\n\nWith precision:\n\nSELECT CURTIME(2);\n+-------------+\n| CURTIME(2) |\n+-------------+\n| 09:49:08.09 |\n+-------------+\n\nURL: https://mariadb.com/kb/en/curtime/ [DATABASE] declaration= category=Information Functions -description=Returns the default (current) database name as a string in\nthe utf8 character set. If there is no default database,\nDATABASE() returns NULL. Within a stored routine, the\ndefault database is the database that the routine is\nassociated with, which is not necessarily the same as the\ndatabase that is the default in the calling context.\n \nSCHEMA() is a synonym for DATABASE().\n \nTo select a default database, the USE statement can be run.\nAnother way to set the default database is specifying its\nname at mysql command line client startup.\n \n\nSELECT DATABASE();\n+------------+\n| DATABASE() |\n+------------+\n| NULL |\n+------------+\n \nUSE test;\n \nDatabase changed\n \nSELECT DATABASE();\n+------------+\n| DATABASE() |\n+------------+\n| test |\n+------------+ +description=Returns the default (current) database name as a string in the utf8 character\nset. If there is no default database, DATABASE() returns NULL. Within a stored\nroutine, the default database is the database that the routine is associated\nwith, which is not necessarily the same as the database that is the default in\nthe calling context.\n\nSCHEMA() is a synonym for DATABASE().\n\nTo select a default database, the USE statement can be run. Another way to set\nthe default database is specifying its name at mariadb command line client\nstartup.\n\nExamples\n--------\n\nSELECT DATABASE();\n+------------+\n| DATABASE() |\n+------------+\n| NULL |\n+------------+\n\nUSE test;\nDatabase changed\n\nSELECT DATABASE();\n+------------+\n| DATABASE() |\n+------------+\n| test |\n+------------+\n\nURL: https://mariadb.com/kb/en/database/ [DATEDIFF] declaration=expr1,expr2 category=Date and Time Functions -description=DATEDIFF() returns (expr1 expr2) expressed\nas a value in days from one date to the other. expr1 and\nexpr2 are date\nor date-and-time expressions. Only the date parts of the\nvalues are used in the\ncalculation.\n \n\nSELECT DATEDIFF(''2007-12-31 23:59:59'',''2007-12-30'');\n+----------------------------------------------+\n| DATEDIFF(''2007-12-31 23:59:59'',''2007-12-30'') |\n+----------------------------------------------+\n| 1 |\n+----------------------------------------------+\n \nSELECT DATEDIFF(''2010-11-30 23:59:59'',''2010-12-31'');\n+----------------------------------------------+\n| DATEDIFF(''2010-11-30 23:59:59'',''2010-12-31'') |\n+----------------------------------------------+\n| -31 |\n+----------------------------------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT NOW();\n+---------------------+\n| NOW() |\n+---------------------+\n| 2011-05-23 10:56:05 |\n+---------------------+\n \nSELECT d, DATEDIFF(NOW(),d) FROM t1;\n \n+---------------------+-------------------+\n| d | DATEDIFF(NOW(),d) |\n+---------------------+-------------------+\n| 2007-01-30 21:31:07 | 1574 |\n| 1983-10-15 06:42:51 | 10082 |\n| 2011-04-21 12:34:56 | 32 |\n| 2011-10-30 06:31:41 | -160 |\n| 2011-01-30 14:03:25 | 113 |\n| 2004-10-07 11:19:34 | 2419 |\n+---------------------+-------------------+ +description=DATEDIFF() returns (expr1 – expr2) expressed as a value in days from one date\nto the other. expr1 and expr2 are date or date-and-time expressions. Only the\ndate parts of the values are used in the calculation.\n\nExamples\n--------\n\nSELECT DATEDIFF('2007-12-31 23:59:59','2007-12-30');\n+----------------------------------------------+\n| DATEDIFF('2007-12-31 23:59:59','2007-12-30') |\n+----------------------------------------------+\n| 1 |\n+----------------------------------------------+\n\nSELECT DATEDIFF('2010-11-30 23:59:59','2010-12-31');\n+----------------------------------------------+\n| DATEDIFF('2010-11-30 23:59:59','2010-12-31') |\n+----------------------------------------------+\n| -31 |\n+----------------------------------------------+\n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n\nSELECT NOW();\n+---------------------+\n| NOW() |\n+---------------------+\n| 2011-05-23 10:56:05 |\n+---------------------+\n\nSELECT d, DATEDIFF(NOW(),d) FROM t1;\n+---------------------+-------------------+\n| d | DATEDIFF(NOW(),d) |\n+---------------------+-------------------+\n| 2007-01-30 21:31:07 | 1574 |\n| 1983-10-15 06:42:51 | 10082 |\n| 2011-04-21 12:34:56 | 32 |\n| 2011-10-30 06:31:41 | -160 |\n| 2011-01-30 14:03:25 | 113 |\n| 2004-10-07 11:19:34 | 2419 |\n+---------------------+-------------------+\n\nURL: https://mariadb.com/kb/en/datediff/ +[DATETIME] +declaration=microsecond precision +category=Data Types +description=A date and time combination.\n\nMariaDB displays DATETIME values in 'YYYY-MM-DD HH:MM:SS.ffffff' format, but\nallows assignment of values to DATETIME columns using either strings or\nnumbers. For details, see date and time literals.\n\nDATETIME columns also accept CURRENT_TIMESTAMP as the default value.\n\nMariaDB 10.1.2 introduced the --mysql56-temporal-format option, on by default,\nwhich allows MariaDB to store DATETMEs using the same low-level format MySQL\n5.6 uses. For more information, see Internal Format, below.\n\nFor storage requirements, see Data Type Storage Requirements.\n\nSupported Values\n----------------\n\nMariaDB stores values that use the DATETIME data type in a format that\nsupports values between 1000-01-01 00:00:00.000000 and 9999-12-31\n23:59:59.999999.\n\nMariaDB can also store microseconds with a precision between 0 and 6. If no\nmicrosecond precision is specified, then 0 is used by default.\n\nMariaDB also supports '0000-00-00' as a special zero-date value, unless\nNO_ZERO_DATE is specified in the SQL_MODE. Similarly, individual components of\na date can be set to 0 (for example: '2015-00-12'), unless NO_ZERO_IN_DATE is\nspecified in the SQL_MODE. In many cases, the result of en expression\ninvolving a zero-date, or a date with zero-parts, is NULL. If the\nALLOW_INVALID_DATES SQL_MODE is enabled, if the day part is in the range\nbetween 1 and 31, the date does not produce any error, even for months that\nhave less than 31 days.\n\nOracle Mode\n-----------\n\nMariaDB starting with 10.3\n--------------------------\nIn Oracle mode from MariaDB 10.3, DATE with a time portion is a synonym for\nDATETIME. See also mariadb_schema.\n\nInternal Format\n---------------\n\nIn MariaDB 10.1.2 a new temporal format was introduced from MySQL 5.6 that\nalters how the TIME, DATETIME and TIMESTAMP columns operate at lower levels.\nThese changes allow these temporal data types to have fractional parts and\nnegative values. You can disable this feature using the\nmysql56_temporal_format system variable.\n\n ... [DATE_ADD] declaration=date,INTERVAL expr unit category=Date and Time Functions -description=Performs date arithmetic. The date argument specifies the\nstarting date or datetime value. expr is an expression\nspecifying the\ninterval value to be added or subtracted from the starting\ndate. expr is a\nstring; it may start with a "-" for negative intervals.\nunit is a\nkeyword indicating the units in which the expression should\nbe interpreted. See Date and Time Units for a complete list\nof permitted units. \n \nSee also DATE_SUB().\n \n\nSELECT ''2008-12-31 23:59:59'' + INTERVAL 1 SECOND;\n \n+-------------------------------------------+\n| ''2008-12-31 23:59:59'' + INTERVAL 1 SECOND |\n+-------------------------------------------+\n| 2009-01-01 00:00:00 |\n+-------------------------------------------+\n \nSELECT INTERVAL 1 DAY + ''2008-12-31'';\n \n+-------------------------------+\n| INTERVAL 1 DAY + ''2008-12-31'' |\n+-------------------------------+\n| 2009-01-01 |\n+-------------------------------+\n \nSELECT ''2005-01-01'' - INTERVAL 1 SECOND;\n \n+----------------------------------+\n| ''2005-01-01'' - INTERVAL 1 SECOND |\n+----------------------------------+\n| 2004-12-31 23:59:59 |\n+----------------------------------+\n \nSELECT DATE_ADD(''2000-12-31 23:59:59'', INTERVAL 1 SECOND);\n+----------------------------------------------------+\n| DATE_ADD(''2000-12-31 23:59:59'', INTERVAL 1 SECOND) |\n+----------------------------------------------------+\n| 2001-01-01 00:00:00 |\n+----------------------------------------------------+\n \nSELECT DATE_ADD(''2010-12-31 23:59:59'', INTERVAL 1 DAY);\n+-------------------------------------------------+\n| DATE_ADD(''2010-12-31 23:59:59'', INTERVAL 1 DAY) |\n+-------------------------------------------------+\n| 2011-01-01 23:59:59 |\n+-------------------------------------------------+\n \nSELECT DATE_ADD(''2100-12-31 23:59:59'', INTERVAL ''1:1''\nMINUTE_SECOND);\n+---------------------------------------------------------------+\n| DATE_ADD(''2100-12-31 23:59:59'', INTERVAL ''1:1''\nMINUTE_SECOND) |\n+---------------------------------------------------------------+\n| 2101-01-01 00:01:00 |\n+---------------------------------------------------------------+\n \nSELECT DATE_ADD(''1900-01-01 00:00:00'', INTERVAL ''-1 10''\nDAY_HOUR);\n+------------------------------------------------------------+\n| DATE_ADD(''1900-01-01 00:00:00'', INTERVAL ''-1 10''\nDAY_HOUR) |\n+------------------------------------------------------------+\n| 1899-12-30 14:00:00 |\n+------------------------------------------------------------+\n \nSELECT DATE_ADD(''1992-12-31 23:59:59.000002'', INTERVAL\n''1.999999'' SECOND_MICROSECOND);\n+--------------------------------------------------------------------------------+\n| DATE_ADD(''1992-12-31 23:59:59.000002'', INTERVAL\n''1.999999'' SECOND_MICROSECOND) |\n+--------------------------------------------------------------------------------+\n| 1993-01-01 00:00:01.000001 |\n+--------------------------------------------------------------------------------+ +description=Performs date arithmetic. The date argument specifies the starting date or\ndatetime value. expr is an expression specifying the interval value to be\nadded or subtracted from the starting date. expr is a string; it may start\nwith a "-" for negative intervals. unit is a keyword indicating the units in\nwhich the expression should be interpreted. See Date and Time Units for a\ncomplete list of permitted units.\n\nExamples\n--------\n\nSELECT '2008-12-31 23:59:59' + INTERVAL 1 SECOND;\n+-------------------------------------------+\n| '2008-12-31 23:59:59' + INTERVAL 1 SECOND |\n+-------------------------------------------+\n| 2009-01-01 00:00:00 |\n+-------------------------------------------+\n\nSELECT INTERVAL 1 DAY + '2008-12-31';\n+-------------------------------+\n| INTERVAL 1 DAY + '2008-12-31' |\n+-------------------------------+\n| 2009-01-01 |\n+-------------------------------+\n\nSELECT '2005-01-01' - INTERVAL 1 SECOND;\n+----------------------------------+\n| '2005-01-01' - INTERVAL 1 SECOND |\n+----------------------------------+\n| 2004-12-31 23:59:59 |\n+----------------------------------+\n\nSELECT DATE_ADD('2000-12-31 23:59:59', INTERVAL 1 SECOND);\n+----------------------------------------------------+\n| DATE_ADD('2000-12-31 23:59:59', INTERVAL 1 SECOND) |\n+----------------------------------------------------+\n| 2001-01-01 00:00:00 |\n+----------------------------------------------------+\n\nSELECT DATE_ADD('2010-12-31 23:59:59', INTERVAL 1 DAY);\n+-------------------------------------------------+\n| DATE_ADD('2010-12-31 23:59:59', INTERVAL 1 DAY) |\n+-------------------------------------------------+\n| 2011-01-01 23:59:59 |\n+-------------------------------------------------+\n\nSELECT DATE_ADD('2100-12-31 23:59:59', INTERVAL '1:1' MINUTE_SECOND);\n+---------------------------------------------------------------+\n| DATE_ADD('2100-12-31 23:59:59', INTERVAL '1:1' MINUTE_SECOND) |\n+---------------------------------------------------------------+\n| 2101-01-01 00:01:00 |\n ... [DATE_FORMAT] declaration=date, format[, locale] category=Date and Time Functions -description=Formats the date value according to the format string. \n \nThe language used for the names is controlled by the value\nof the lc_time_names system variable. See server locale for\nmore on the supported locales.\n \nThe options that can be used by DATE_FORMAT(), as well as\nits inverse STR_TO_DATE() and the FROM_UNIXTIME() function,\nare:\n \nOption | Description | \n \n%a | Short weekday name in current locale (Variable\nlc_time_names). | \n \n%b | Short form month name in current locale. For locale\nen_US this is one of:\nJan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov or Dec. | \n \n%c | Month with 1 or 2 digits. | \n \n%D | Day with English suffix ''th'', ''nd'', ''st'' or\n''rd''''. (1st, 2nd, 3rd...). | \n \n%d | Day with 2 digits. | \n \n%e | Day with 1 or 2 digits. | \n \n%f | Sub seconds 6 digits. | \n \n%H | Hour with 2 digits between 00-23. | \n \n%h | Hour with 2 digits between 01-12. | \n \n%I | Hour with 2 digits between 01-12. | \n \n%i | Minute with 2 digits. | \n \n%j | Day of the year (001-366) | \n \n%k | Hour with 1 digits between 0-23. | \n \n%l | Hour with 1 digits between 1-12. | \n \n%M | Full month name in current locale (Variable\nlc_time_names). | \n \n%m | Month with 2 digits. | \n \n%p | AM/PM according to current locale (Variable\nlc_time_names). | \n \n%r | Time in 12 hour format, followed by AM/PM. Short for\n''%I:%i:%S %p''. | \n \n%S | Seconds with 2 digits. | \n \n%s | Seconds with 2 digits. | \n \n%T | Time in 24 hour format. Short for ''%H:%i:%S''. | \n \n%U | Week number (00-53), when first day of the week is\nSunday. | \n \n%u | Week number (00-53), when first day of the week is\nMonday. | \n \n%V | Week number (01-53), when first day of the week is\nSunday. Used with %X. | \n \n%v | Week number (01-53), when first day of the week is\nMonday. Used with %x. | \n \n%W | Full weekday name in current locale (Variable\nlc_time_names). | \n \n%w | Day of the week. 0 = Sunday, 6 = Saturday. | \n \n%X | Year with 4 digits when first day of the week is\nSunday. Used with %V. | \n \n%x | Year with 4 digits when first day of the week is\nMonday. Used with %v. | \n \n%Y | Year with 4 digits. | \n \n%y | Year with 2 digits. | \n \n%# | For str_to_date(), skip all numbers. | \n \n%. | For str_to_date(), skip all punctation characters. | \n \n%@ | For str_to_date(), skip all alpha characters. | \n \n%% | A literal % character. | \n \nTo get a date in one of the standard formats, GET_FORMAT()\ncan be used.\n \n\nSELECT DATE_FORMAT(''2009-10-04 22:23:00'', ''%W %M %Y'');\n+------------------------------------------------+\n| DATE_FORMAT(''2009-10-04 22:23:00'', ''%W %M %Y'') |\n+------------------------------------------------+\n| Sunday October 2009 |\n+------------------------------------------------+\n \nSELECT DATE_FORMAT(''2007-10-04 22:23:00'', ''%H:%i:%s'');\n+------------------------------------------------+\n| DATE_FORMAT(''2007-10-04 22:23:00'', ''%H:%i:%s'') |\n+------------------------------------------------+\n| 22:23:00 |\n+------------------------------------------------+\n \nSELECT DATE_FORMAT(''1900-10-04 22:23:00'', ''%D %y %a %d %m\n%b %j'');\n+------------------------------------------------------------+\n| DATE_FORMAT(''1900-10-04 22:23:00'', ''%D %y %a %d %m %b\n%j'') |\n+------------------------------------------------------------+\n| 4th 00 Thu 04 10 Oct 277 |\n+------------------------------------------------------------+\n \nSELECT DATE_FORMAT(''1997-10-04 22:23:00'', ''%H %k %I %r %T\n%S %w'');\n+------------------------------------------------------------+\n| DATE_FORMAT(''1997-10-04 22:23:00'', ''%H %k %I %r %T %S\n%w'') |\n+------------------------------------------------------------+\n| 22 22 10 10:23:00 PM 22:23:00 00 6 |\n+------------------------------------------------------------+\n \nSELECT DATE_FORMAT(''1999-01-01'', ''%X %V'');\n+------------------------------------+\n| DATE_FORMAT(''1999-01-01'', ''%X %V'') |\n+------------------------------------+\n| 1998 52 |\n+------------------------------------+\n \nSELECT DATE_FORMAT(''2006-06-00'', ''%d'');\n+---------------------------------+\n| DATE_FORMAT(''2006-06-00'', ''%d'') |\n+---------------------------------+\n| 00 |\n+---------------------------------+\n \nOptionally, the locale can be explicitly specified as the\nthird DATE_FORMAT() argument. Doing so makes the function\nindependent from the session settings, and the three\nargument version of DATE_FORMAT() can be used in virtual\nindexed and persistent generated-columns:\n \nSELECT DATE_FORMAT(''2006-01-01'', ''%W'', ''el_GR'');\n+------------------------------------------+\n| DATE_FORMAT(''2006-01-01'', ''%W'', ''el_GR'') |\n+------------------------------------------+\n| ??????? |\n+------------------------------------------+ +description=Formats the date value according to the format string.\n\nThe language used for the names is controlled by the value of the\nlc_time_names system variable. See server locale for more on the supported\nlocales.\n\nThe options that can be used by DATE_FORMAT(), as well as its inverse\nSTR_TO_DATE() and the FROM_UNIXTIME() function, are:\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| %a | Short weekday name in current locale |\n| | (Variable lc_time_names). |\n+---------------------------+------------------------------------------------+\n| %b | Short form month name in current locale. For |\n| | locale en_US this is one of: |\n| | Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov |\n| | or Dec. |\n+---------------------------+------------------------------------------------+\n| %c | Month with 1 or 2 digits. |\n+---------------------------+------------------------------------------------+\n| %D | Day with English suffix 'th', 'nd', 'st' or |\n| | 'rd''. (1st, 2nd, 3rd...). |\n+---------------------------+------------------------------------------------+\n| %d | Day with 2 digits. |\n+---------------------------+------------------------------------------------+\n| %e | Day with 1 or 2 digits. |\n+---------------------------+------------------------------------------------+\n| %f | Microseconds 6 digits. |\n+---------------------------+------------------------------------------------+\n| %H | Hour with 2 digits between 00-23. |\n+---------------------------+------------------------------------------------+\n| %h | Hour with 2 digits between 01-12. |\n+---------------------------+------------------------------------------------+\n| %I | Hour with 2 digits between 01-12. |\n+---------------------------+------------------------------------------------+\n| %i | Minute with 2 digits. |\n+---------------------------+------------------------------------------------+\n| %j | Day of the year (001-366) |\n+---------------------------+------------------------------------------------+\n| %k | Hour with 1 digits between 0-23. |\n+---------------------------+------------------------------------------------+\n| %l | Hour with 1 digits between 1-12. |\n+---------------------------+------------------------------------------------+\n| %M | Full month name in current locale (Variable |\n| | lc_time_names). |\n+---------------------------+------------------------------------------------+\n| %m | Month with 2 digits. |\n+---------------------------+------------------------------------------------+\n ... [DATE_SUB] declaration=date,INTERVAL expr unit category=Date and Time Functions -description=Performs date arithmetic. The date argument specifies the\nstarting date or datetime value. expr is an expression\nspecifying the\ninterval value to be added or subtracted from the starting\ndate. expr is a\nstring; it may start with a "-" for negative intervals.\nunit is a\nkeyword indicating the units in which the expression should\nbe interpreted. See Date and Time Units for a complete list\nof permitted units. \n \nSee also DATE_ADD().\n \n\nSELECT DATE_SUB(''1998-01-02'', INTERVAL 31 DAY);\n+-----------------------------------------+\n| DATE_SUB(''1998-01-02'', INTERVAL 31 DAY) |\n+-----------------------------------------+\n| 1997-12-02 |\n+-----------------------------------------+\n \nSELECT DATE_SUB(''2005-01-01 00:00:00'', INTERVAL ''1\n1:1:1'' DAY_SECOND);\n+----------------------------------------------------------------+\n| DATE_SUB(''2005-01-01 00:00:00'', INTERVAL ''1 1:1:1''\nDAY_SECOND) |\n+----------------------------------------------------------------+\n| 2004-12-30 22:58:59 |\n+----------------------------------------------------------------+ +description=Performs date arithmetic. The date argument specifies the starting date or\ndatetime value. expr is an expression specifying the interval value to be\nadded or subtracted from the starting date. expr is a string; it may start\nwith a "-" for negative intervals. unit is a keyword indicating the units in\nwhich the expression should be interpreted. See Date and Time Units for a\ncomplete list of permitted units.\n\nSee also DATE_ADD().\n\nExamples\n--------\n\nSELECT DATE_SUB('1998-01-02', INTERVAL 31 DAY);\n+-----------------------------------------+\n| DATE_SUB('1998-01-02', INTERVAL 31 DAY) |\n+-----------------------------------------+\n| 1997-12-02 |\n+-----------------------------------------+\n\nSELECT DATE_SUB('2005-01-01 00:00:00', INTERVAL '1 1:1:1' DAY_SECOND);\n+----------------------------------------------------------------+\n| DATE_SUB('2005-01-01 00:00:00', INTERVAL '1 1:1:1' DAY_SECOND) |\n+----------------------------------------------------------------+\n| 2004-12-30 22:58:59 |\n+----------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/date_sub/ +[DAY] +declaration=date +category=Date and Time Functions +description=DAY() is a synonym for DAYOFMONTH().\n\nURL: https://mariadb.com/kb/en/day/ [DAYNAME] declaration=date category=Date and Time Functions -description=Returns the name of the weekday for date. The language used\nfor the name is controlled by the value\nof the lc_time_names system variable. See server locale for\nmore on the supported locales.\n \n\nSELECT DAYNAME(''2007-02-03'');\n+-----------------------+\n| DAYNAME(''2007-02-03'') |\n+-----------------------+\n| Saturday |\n+-----------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT d, DAYNAME(d) FROM t1;\n \n+---------------------+------------+\n| d | DAYNAME(d) |\n+---------------------+------------+\n| 2007-01-30 21:31:07 | Tuesday |\n| 1983-10-15 06:42:51 | Saturday |\n| 2011-04-21 12:34:56 | Thursday |\n| 2011-10-30 06:31:41 | Sunday |\n| 2011-01-30 14:03:25 | Sunday |\n| 2004-10-07 11:19:34 | Thursday |\n+---------------------+------------+\n \nChanging the locale:\n \nSET lc_time_names = ''fr_CA'';\n \nSELECT DAYNAME(''2013-04-01'');\n+-----------------------+\n| DAYNAME(''2013-04-01'') |\n+-----------------------+\n| lundi |\n+-----------------------+ +description=Returns the name of the weekday for date. The language used for the name is\ncontrolled by the value of the lc_time_names system variable. See server\nlocale for more on the supported locales.\n\nExamples\n--------\n\nSELECT DAYNAME('2007-02-03');\n+-----------------------+\n| DAYNAME('2007-02-03') |\n+-----------------------+\n| Saturday |\n+-----------------------+\n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n\nSELECT d, DAYNAME(d) FROM t1;\n+---------------------+------------+\n| d | DAYNAME(d) |\n+---------------------+------------+\n| 2007-01-30 21:31:07 | Tuesday |\n| 1983-10-15 06:42:51 | Saturday |\n| 2011-04-21 12:34:56 | Thursday |\n| 2011-10-30 06:31:41 | Sunday |\n| 2011-01-30 14:03:25 | Sunday |\n| 2004-10-07 11:19:34 | Thursday |\n+---------------------+------------+\n\nChanging the locale:\n\nSET lc_time_names = 'fr_CA';\n\nSELECT DAYNAME('2013-04-01');\n+-----------------------+\n| DAYNAME('2013-04-01') |\n+-----------------------+\n| lundi |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/dayname/ [DAYOFMONTH] declaration=date category=Date and Time Functions -description=Returns the day of the month for date, in the range 1 to 31,\nor 0\nfor dates such as ''0000-00-00'' or ''2008-00-00'' which\nhave a zero day\npart.\n \nDAY() is a synonym.\n \n\nSELECT DAYOFMONTH(''2007-02-03'');\n+--------------------------+\n| DAYOFMONTH(''2007-02-03'') |\n+--------------------------+\n| 3 |\n+--------------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT d FROM t1 where DAYOFMONTH(d) = 30;\n \n+---------------------+\n| d |\n+---------------------+\n| 2007-01-30 21:31:07 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n+---------------------+ +description=Returns the day of the month for date, in the range 1 to 31, or 0 for dates\nsuch as '0000-00-00' or '2008-00-00' which have a zero day part.\n\nDAY() is a synonym.\n\nExamples\n--------\n\nSELECT DAYOFMONTH('2007-02-03');\n+--------------------------+\n| DAYOFMONTH('2007-02-03') |\n+--------------------------+\n| 3 |\n+--------------------------+\n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n\nSELECT d FROM t1 where DAYOFMONTH(d) = 30;\n+---------------------+\n| d |\n+---------------------+\n| 2007-01-30 21:31:07 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/dayofmonth/ [DAYOFWEEK] declaration=date category=Date and Time Functions -description=Returns the day of the week index for the date (1 = Sunday,\n2 = Monday, ..., 7 =\nSaturday). These index values correspond to the ODBC\nstandard.\n \nThis contrasts with WEEKDAY() which follows a different\nindex numbering\n(0 = Monday, 1 = Tuesday, ... 6 = Sunday).\n \n\nSELECT DAYOFWEEK(''2007-02-03'');\n+-------------------------+\n| DAYOFWEEK(''2007-02-03'') |\n+-------------------------+\n| 7 |\n+-------------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT d, DAYNAME(d), DAYOFWEEK(d), WEEKDAY(d) from t1;\n \n+---------------------+------------+--------------+------------+\n| d | DAYNAME(d) | DAYOFWEEK(d) | WEEKDAY(d) |\n+---------------------+------------+--------------+------------+\n| 2007-01-30 21:31:07 | Tuesday | 3 | 1 |\n| 1983-10-15 06:42:51 | Saturday | 7 | 5 |\n| 2011-04-21 12:34:56 | Thursday | 5 | 3 |\n| 2011-10-30 06:31:41 | Sunday | 1 | 6 |\n| 2011-01-30 14:03:25 | Sunday | 1 | 6 |\n| 2004-10-07 11:19:34 | Thursday | 5 | 3 |\n+---------------------+------------+--------------+------------+ +description=Returns the day of the week index for the date (1 = Sunday, 2 = Monday, ..., 7\n= Saturday). These index values correspond to the ODBC standard.\n\nThis contrasts with WEEKDAY() which follows a different index numbering (0 =\nMonday, 1 = Tuesday, ... 6 = Sunday).\n\nExamples\n--------\n\nSELECT DAYOFWEEK('2007-02-03');\n+-------------------------+\n| DAYOFWEEK('2007-02-03') |\n+-------------------------+\n| 7 |\n+-------------------------+\n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n\nSELECT d, DAYNAME(d), DAYOFWEEK(d), WEEKDAY(d) from t1;\n+---------------------+------------+--------------+------------+\n| d | DAYNAME(d) | DAYOFWEEK(d) | WEEKDAY(d) |\n+---------------------+------------+--------------+------------+\n| 2007-01-30 21:31:07 | Tuesday | 3 | 1 |\n| 1983-10-15 06:42:51 | Saturday | 7 | 5 |\n| 2011-04-21 12:34:56 | Thursday | 5 | 3 |\n| 2011-10-30 06:31:41 | Sunday | 1 | 6 |\n| 2011-01-30 14:03:25 | Sunday | 1 | 6 |\n| 2004-10-07 11:19:34 | Thursday | 5 | 3 |\n+---------------------+------------+--------------+------------+\n\nURL: https://mariadb.com/kb/en/dayofweek/ [DAYOFYEAR] declaration=date category=Date and Time Functions -description=Returns the day of the year for date, in the range 1 to 366.\n \n\nSELECT DAYOFYEAR(''2018-02-16'');\n+-------------------------+\n| DAYOFYEAR(''2018-02-16'') |\n+-------------------------+\n| 47 |\n+-------------------------+ -[DAY] -declaration=date -category=Date and Time Functions -description=DAY() is a synonym for DAYOFMONTH(). +description=Returns the day of the year for date, in the range 1 to 366.\n\nExamples\n--------\n\nSELECT DAYOFYEAR('2018-02-16');\n+-------------------------+\n| DAYOFYEAR('2018-02-16') |\n+-------------------------+\n| 47 |\n+-------------------------+\n\nURL: https://mariadb.com/kb/en/dayofyear/ +[DECIMAL] +declaration=M[,D] +category=Data Types +description=A packed "exact" fixed-point number. M is the total number of digits (the\nprecision) and D is the number of digits after the decimal point (the scale).\n\n* The decimal point and (for negative numbers) the "-" sign are not\ncounted in M. \n* If D is 0, values have no decimal point or fractional\npart and on INSERT the value will be rounded to the nearest DECIMAL. \n* The maximum number of digits (M) for DECIMAL is 65. \n* The maximum number of supported decimals (D) is 30 before MariadB 10.2.1 and\n38 afterwards. \n* If D is omitted, the default is 0. If M is omitted, the default is 10.\n\nUNSIGNED, if specified, disallows negative values.\n\nZEROFILL, if specified, pads the number with zeros, up to the total number of\ndigits specified by M.\n\nAll basic calculations (+, -, *, /) with DECIMAL columns are done with a\nprecision of 65 digits.\n\nFor more details on the attributes, see Numeric Data Type Overview.\n\nDEC, NUMERIC and FIXED are synonyms, as well as NUMBER in Oracle mode from\nMariaDB 10.3.\n\nExamples\n--------\n\nCREATE TABLE t1 (d DECIMAL UNSIGNED ZEROFILL);\n\nINSERT INTO t1 VALUES (1),(2),(3),(4.0),(5.2),(5.7);\nQuery OK, 6 rows affected, 2 warnings (0.16 sec)\nRecords: 6 Duplicates: 0 Warnings: 2\n\nNote (Code 1265): Data truncated for column 'd' at row 5\nNote (Code 1265): Data truncated for column 'd' at row 6\n\nSELECT * FROM t1;\n+------------+\n| d |\n+------------+\n| 0000000001 |\n| 0000000002 |\n| 0000000003 |\n| 0000000004 |\n| 0000000005 |\n| 0000000006 |\n+------------+\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n ... [DECODE] declaration=crypt_str,pass_str category=Encryption Functions -description=Decrypts the encrypted string crypt_str using pass_str as\nthe\npassword. crypt_str should be a string returned from\nENCODE(). The resulting string will be the original string\nonly if pass_str is the same. +description=In the default mode, DECODE decrypts the encrypted string crypt_str using\npass_str as the password. crypt_str should be a string returned from ENCODE().\nThe resulting string will be the original string only if pass_str is the same.\n\nIn Oracle mode from MariaDB 10.3.2, DECODE compares expr to the search\nexpressions, in order. If it finds a match, the corresponding result\nexpression is returned. If no matches are found, the default expression is\nreturned, or NULL if no default is provided.\n\nNULLs are treated as equivalent.\n\nDECODE_ORACLE is a synonym for the Oracle-mode version of the function, and is\navailable in all modes.\n\nExamples\n--------\n\nFrom MariaDB 10.3.2:\n\nSELECT DECODE_ORACLE(2+1,3*1,'found1',3*2,'found2','default');\n+--------------------------------------------------------+\n| DECODE_ORACLE(2+1,3*1,'found1',3*2,'found2','default') |\n+--------------------------------------------------------+\n| found1 |\n+--------------------------------------------------------+\n\nSELECT DECODE_ORACLE(2+4,3*1,'found1',3*2,'found2','default');\n+--------------------------------------------------------+\n| DECODE_ORACLE(2+4,3*1,'found1',3*2,'found2','default') |\n+--------------------------------------------------------+\n| found2 |\n+--------------------------------------------------------+\n\nSELECT DECODE_ORACLE(2+2,3*1,'found1',3*2,'found2','default');\n+--------------------------------------------------------+\n| DECODE_ORACLE(2+2,3*1,'found1',3*2,'found2','default') |\n+--------------------------------------------------------+\n| default |\n+--------------------------------------------------------+\n\nNulls are treated as equivalent:\n\nSELECT DECODE_ORACLE(NULL,NULL,'Nulls are equivalent','Nulls are not\nequivalent');\n+----------------------------------------------------------------------------+\n| DECODE_ORACLE(NULL,NULL,'Nulls are equivalent','Nulls are not equivalent') |\n+----------------------------------------------------------------------------+\n| Nulls are equivalent |\n+----------------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/decode/ [DECODE_HISTOGRAM] declaration=hist_type,histogram category=Information Functions -description=Returns a string of comma separated numeric values\ncorresponding to a probability distribution represented by\nthe histogram of type hist_type (SINGLE_PREC_HB or\nDOUBLE_PREC_HB). The hist_type and histogram would be\ncommonly used from the mysql.column_stats table.\n \nSee Histogram Based Statistics for details.\n \n\nCREATE TABLE origin (\n i INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,\n v INT UNSIGNED NOT NULL\n);\n \nINSERT INTO origin(v) VALUES \n (1),(2),(3),(4),(5),(10),(20),\n (30),(40),(50),(60),(70),(80),\n (90),(100),(200),(400),(800);\n \nSET histogram_size=10,histogram_type=SINGLE_PREC_HB;\n \nANALYZE TABLE origin PERSISTENT FOR ALL;\n \n+-------------+---------+----------+-----------------------------------------+\n| Table | Op | Msg_type | Msg_text |\n+-------------+---------+----------+-----------------------------------------+\n| test.origin | analyze | status | Engine-independent\nstatistics collected |\n| test.origin | analyze | status | OK |\n+-------------+---------+----------+-----------------------------------------+\n \nSELECT db_name,table_name,column_name,hist_type,\n hex(histogram),decode_histogram(hist_type,histogram) \n FROM mysql.column_stats WHERE db_name=''test'' and\ntable_name=''origin'';\n \n+---------+------------+-------------+----------------+----------------------+-------------------------------------------------------------------+\n| db_name | table_name | column_name | hist_type |\nhex(histogram) | decode_histogram(hist_type,histogram) |\n+---------+------------+-------------+----------------+----------------------+-------------------------------------------------------------------+\n| test | origin | i | SINGLE_PREC_HB | 0F2D3C5A7887A5C3D2F0\n|\n0.059,0.118,0.059,0.118,0.118,0.059,0.118,0.118,0.059,0.118,0.059\n|\n| test | origin | v | SINGLE_PREC_HB | 000001060C0F161C1F7F\n|\n0.000,0.000,0.004,0.020,0.024,0.012,0.027,0.024,0.012,0.376,0.502\n|\n+---------+------------+-------------+----------------+----------------------+-------------------------------------------------------------------+\n \nSET histogram_size=20,histogram_type=DOUBLE_PREC_HB;\n \nANALYZE TABLE origin PERSISTENT FOR ALL;\n \n+-------------+---------+----------+-----------------------------------------+\n| Table | Op | Msg_type | Msg_text |\n+-------------+---------+----------+-----------------------------------------+\n| test.origin | analyze | status | Engine-independent\nstatistics collected |\n| test.origin | analyze | status | OK |\n+-------------+---------+----------+-----------------------------------------+\n \nSELECT db_name,table_name,column_name,\n hist_type,hex(histogram),decode_histogram(hist_type,histogram)\n\n FROM mysql.column_stats WHERE db_name=''test'' and\ntable_name=''origin'';\n \n+---------+------------+-------------+----------------+------------------------------------------+-----------------------------------------------------------------------------------------+\n| db_name | table_name | column_name | hist_type |\nhex(histogram) | decode_histogram(hist_type,histogram) |\n+---------+------------+-------------+----------------+------------------------------------------+-----------------------------------------------------------------------------------------+\n| test | origin | i | DOUBLE_PREC_HB |\n0F0F2D2D3C3C5A5A78788787A5A5C3C3D2D2F0F0 |\n0.05882,0.11765,0.05882,0.11765,0.11765,0.05882,0.11765,0.11765,0.05882,0.11765,0.05882\n|\n| test | origin | v | DOUBLE_PREC_HB |\n5200F600480116067E0CB30F1B16831CB81FD67F |\n0.00125,0.00250,0.00125,0.01877,0.02502,0.01253,0.02502,0.02502,0.01253,0.37546,0.50063\n| +description=Returns a string of comma separated numeric values corresponding to a\nprobability distribution represented by the histogram of type hist_type\n(SINGLE_PREC_HB or DOUBLE_PREC_HB). The hist_type and histogram would be\ncommonly used from the mysql.column_stats table.\n\nSee Histogram Based Statistics for details.\n\nExamples\n--------\n\nCREATE TABLE origin (\n i INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,\n v INT UNSIGNED NOT NULL\n);\n\nINSERT INTO origin(v) VALUES \n (1),(2),(3),(4),(5),(10),(20),\n (30),(40),(50),(60),(70),(80),\n (90),(100),(200),(400),(800);\n\nSET histogram_size=10,histogram_type=SINGLE_PREC_HB;\n\nANALYZE TABLE origin PERSISTENT FOR ALL;\n+-------------+---------+----------+-----------------------------------------+\n| Table | Op | Msg_type | Msg_text |\n+-------------+---------+----------+-----------------------------------------+\n| test.origin | analyze | status | Engine-independent statistics collected |\n| test.origin | analyze | status | OK |\n+-------------+---------+----------+-----------------------------------------+\n\nSELECT db_name,table_name,column_name,hist_type,\n hex(histogram),decode_histogram(hist_type,histogram)\n FROM mysql.column_stats WHERE db_name='test' and table_name='origin';\n+---------+------------+-------------+----------------+----------------------+-\n-----------------------------------------------------------------+\n| db_name | table_name | column_name | hist_type | hex(histogram) |\ndecode_histogram(hist_type,histogram) |\n+---------+------------+-------------+----------------+----------------------+-\n-----------------------------------------------------------------+\n| test | origin | i | SINGLE_PREC_HB | 0F2D3C5A7887A5C3D2F0 |\n0.059,0.118,0.059,0.118,0.118,0.059,0.118,0.118,0.059,0.118,0.059 |\n| test | origin | v | SINGLE_PREC_HB | 000001060C0F161C1F7F |\n0.000,0.000,0.004,0.020,0.024,0.012,0.027,0.024,0.012,0.376,0.502 |\n+---------+------------+-------------+----------------+----------------------+-\n-----------------------------------------------------------------+\n\nSET histogram_size=20,histogram_type=DOUBLE_PREC_HB;\n\nANALYZE TABLE origin PERSISTENT FOR ALL;\n+-------------+---------+----------+-----------------------------------------+\n ... [DEFAULT] declaration=col_name category=Information Functions -description=Returns the default value for a table column. If the column\nhas no default value, NULL is returned.\nFor integer columns using AUTO_INCREMENT, 0 is returned.\n \nWhen using DEFAULT as a value to set in an INSERT or UPDATE\nstatement, you can use the bare keyword DEFAULT without the\nparentheses and argument to\nrefer to the column in context. You can only use DEFAULT as\na bare keyword if you are using it\nalone without a surrounding expression or function.\n \n\nSelect only non-default values for a column:\n \nSELECT i FROM t WHERE i != DEFAULT(i);\n \nUpdate values to be one greater than the default value:\n \nUPDATE t SET i = DEFAULT(i)+1 WHERE i +description=Returns the default value for a table column. If the column has no default\nvalue (and is not NULLABLE - NULLABLE fields have a NULL default), an error is\nreturned.\n\nFor integer columns using AUTO_INCREMENT, 0 is returned.\n\nWhen using DEFAULT as a value to set in an INSERT or UPDATE statement, you can\nuse the bare keyword DEFAULT without the parentheses and argument to refer to\nthe column in context. You can only use DEFAULT as a bare keyword if you are\nusing it alone without a surrounding expression or function.\n\nExamples\n--------\n\nSelect only non-default values for a column:\n\nSELECT i FROM t WHERE i != DEFAULT(i);\n\nUpdate values to be one greater than the default value:\n\nUPDATE t SET i = DEFAULT(i)+1 WHERE i < 100;\n\nWhen referring to the default value exactly in UPDATE or INSERT, you can omit\nthe argument:\n\nINSERT INTO t (i) VALUES (DEFAULT);\nUPDATE t SET i = DEFAULT WHERE i < 100;\n\nCREATE OR REPLACE TABLE t (\n i INT NOT NULL AUTO_INCREMENT,\n j INT NOT NULL,\n k INT DEFAULT 3,\n l INT NOT NULL DEFAULT 4,\n m INT,\n PRIMARY KEY (i)\n);\n\nDESC t;\n+-------+---------+------+-----+---------+----------------+\n| Field | Type | Null | Key | Default | Extra |\n+-------+---------+------+-----+---------+----------------+\n| i | int(11) | NO | PRI | NULL | auto_increment |\n| j | int(11) | NO | | NULL | |\n| k | int(11) | YES | | 3 | |\n| l | int(11) | NO | | 4 | |\n| m | int(11) | YES | | NULL | |\n+-------+---------+------+-----+---------+----------------+\n\nINSERT INTO t (j) VALUES (1);\nINSERT INTO t (j,m) VALUES (2,2);\n ... [DEGREES] declaration=X category=Numeric Functions -description=Returns the argument X, converted from radians to degrees.\n \nThis is the converse of the RADIANS() function.\n \n\nSELECT DEGREES(PI());\n+---------------+\n| DEGREES(PI()) |\n+---------------+\n| 180 |\n+---------------+\n \nSELECT DEGREES(PI() / 2);\n+-------------------+\n| DEGREES(PI() / 2) |\n+-------------------+\n| 90 |\n+-------------------+\n \nSELECT DEGREES(45);\n+-----------------+\n| DEGREES(45) |\n+-----------------+\n| 2578.3100780887 |\n+-----------------+ -[DENSE_RANK1] -name=DENSE_RANK +description=Returns the argument X, converted from radians to degrees.\n\nThis is the converse of the RADIANS() function.\n\nExamples\n--------\n\nSELECT DEGREES(PI());\n+---------------+\n| DEGREES(PI()) |\n+---------------+\n| 180 |\n+---------------+\n\nSELECT DEGREES(PI() / 2);\n+-------------------+\n| DEGREES(PI() / 2) |\n+-------------------+\n| 90 |\n+-------------------+\n\nSELECT DEGREES(45);\n+-----------------+\n| DEGREES(45) |\n+-----------------+\n| 2578.3100780887 |\n+-----------------+\n\nURL: https://mariadb.com/kb/en/degrees/ +[DENSE_RANK] declaration= category=Window Functions -description=DENSE_RANK() is a window function that displays the number\nof a given row, starting at one and following the ORDER BY\nsequence of the window function, with identical values\nreceiving the same result. Unlike the RANK() function, there\nare no skipped values if the preceding results are\nidentical. It is also similar to the ROW_NUMBER() function\nexcept that in that function, identical values will receive\na different row number for each result.\n \n\nThe distinction between DENSE_RANK(), RANK() and\nROW_NUMBER():\n \nCREATE TABLE student(course VARCHAR(10), mark int, name\nvarchar(10));\n \nINSERT INTO student VALUES \n (''Maths'', 60, ''Thulile''),\n (''Maths'', 60, ''Pritha''),\n (''Maths'', 70, ''Voitto''),\n (''Maths'', 55, ''Chun''),\n (''Biology'', 60, ''Bilal''),\n (''Biology'', 70, ''Roger'');\n \nSELECT \n RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS\nrank, \n DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC)\nAS dense_rank, \n ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC)\nAS row_num, \n course, mark, name \nFROM student ORDER BY course, mark DESC;\n \n+------+------------+---------+---------+------+---------+\n| rank | dense_rank | row_num | course | mark | name |\n+------+------------+---------+---------+------+---------+\n| 1 | 1 | 1 | Biology | 70 | Roger |\n| 2 | 2 | 2 | Biology | 60 | Bilal |\n| 1 | 1 | 1 | Maths | 70 | Voitto |\n| 2 | 2 | 2 | Maths | 60 | Thulile |\n| 2 | 2 | 3 | Maths | 60 | Pritha |\n| 4 | 3 | 4 | Maths | 55 | Chun |\n+------+------------+---------+---------+------+---------+ -[DENSE_RANK2] -name=DENSE_RANK -declaration=[ PARTITION BY partition_expression ] [ ORDER BY order_list ] -category=Window Functions -description=DENSE_RANK() is a window function that displays the number\nof a given row, starting at one and following the ORDER BY\nsequence of the window function, with identical values\nreceiving the same result. Unlike the RANK() function, there\nare no skipped values if the preceding results are\nidentical. It is also similar to the ROW_NUMBER() function\nexcept that in that function, identical values will receive\na different row number for each result.\n \n\nThe distinction between DENSE_RANK(), RANK() and\nROW_NUMBER():\n \nCREATE TABLE student(course VARCHAR(10), mark int, name\nvarchar(10));\n \nINSERT INTO student VALUES \n (''Maths'', 60, ''Thulile''),\n (''Maths'', 60, ''Pritha''),\n (''Maths'', 70, ''Voitto''),\n (''Maths'', 55, ''Chun''),\n (''Biology'', 60, ''Bilal''),\n (''Biology'', 70, ''Roger'');\n \nSELECT \n RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS\nrank, \n DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC)\nAS dense_rank, \n ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC)\nAS row_num, \n course, mark, name \nFROM student ORDER BY course, mark DESC;\n \n+------+------------+---------+---------+------+---------+\n| rank | dense_rank | row_num | course | mark | name |\n+------+------------+---------+---------+------+---------+\n| 1 | 1 | 1 | Biology | 70 | Roger |\n| 2 | 2 | 2 | Biology | 60 | Bilal |\n| 1 | 1 | 1 | Maths | 70 | Voitto |\n| 2 | 2 | 2 | Maths | 60 | Thulile |\n| 2 | 2 | 3 | Maths | 60 | Pritha |\n| 4 | 3 | 4 | Maths | 55 | Chun |\n+------+------------+---------+---------+------+---------+ +description=DENSE_RANK() is a window function that displays the number of a given row,\nstarting at one and following the ORDER BY sequence of the window function,\nwith identical values receiving the same result. Unlike the RANK() function,\nthere are no skipped values if the preceding results are identical. It is also\nsimilar to the ROW_NUMBER() function except that in that function, identical\nvalues will receive a different row number for each result.\n\nExamples\n--------\n\nThe distinction between DENSE_RANK(), RANK() and ROW_NUMBER():\n\nCREATE TABLE student(course VARCHAR(10), mark int, name varchar(10));\n\nINSERT INTO student VALUES \n ('Maths', 60, 'Thulile'),\n ('Maths', 60, 'Pritha'),\n ('Maths', 70, 'Voitto'),\n ('Maths', 55, 'Chun'),\n ('Biology', 60, 'Bilal'),\n ('Biology', 70, 'Roger');\n\nSELECT \n RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS rank,\n DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS dense_rank,\n ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC) AS row_num,\n course, mark, name\nFROM student ORDER BY course, mark DESC;\n+------+------------+---------+---------+------+---------+\n| rank | dense_rank | row_num | course | mark | name |\n+------+------------+---------+---------+------+---------+\n| 1 | 1 | 1 | Biology | 70 | Roger |\n| 2 | 2 | 2 | Biology | 60 | Bilal |\n| 1 | 1 | 1 | Maths | 70 | Voitto |\n| 2 | 2 | 2 | Maths | 60 | Thulile |\n| 2 | 2 | 3 | Maths | 60 | Pritha |\n| 4 | 3 | 4 | Maths | 55 | Chun |\n+------+------------+---------+---------+------+---------+\n\nURL: https://mariadb.com/kb/en/dense_rank/ [DES_DECRYPT] declaration=crypt_str[,key_str] category=Encryption Functions -description=Decrypts a string encrypted with DES_ENCRYPT(). If an error\noccurs,\nthis function returns NULL.\n \nThis function works only if MariaDB has been configured with\nTLS\nsupport.\n \nIf no key_str argument is given, DES_DECRYPT() examines the\nfirst byte\nof the encrypted string to determine the DES key number that\nwas used\nto encrypt the original string, and then reads the key from\nthe DES\nkey file to decrypt the message. For this to work, the user\nmust have\nthe SUPER privilege. The key file can be specified with the\n--des-key-file server option.\n \nIf you pass this function a key_str argument, that string is\nused as\nthe key for decrypting the message.\n \nIf the crypt_str argument does not appear to be an encrypted\nstring,\nMariaDB returns the given crypt_str. +description=Decrypts a string encrypted with DES_ENCRYPT(). If an error occurs, this\nfunction returns NULL.\n\nThis function works only if MariaDB has been configured with TLS support.\n\nIf no key_str argument is given, DES_DECRYPT() examines the first byte of the\nencrypted string to determine the DES key number that was used to encrypt the\noriginal string, and then reads the key from the DES key file to decrypt the\nmessage. For this to work, the user must have the SUPER privilege. The key\nfile can be specified with the --des-key-file server option.\n\nIf you pass this function a key_str argument, that string is used as the key\nfor decrypting the message.\n\nIf the crypt_str argument does not appear to be an encrypted string, MariaDB\nreturns the given crypt_str.\n\nURL: https://mariadb.com/kb/en/des_decrypt/ [DES_ENCRYPT] declaration=str[,{key_num|key_str}] category=Encryption Functions -description=Encrypts the string with the given key using the Triple-DES\nalgorithm.\n \nThis function works only if MariaDB has been configured with\nTLS support.\n \nThe encryption key to use is chosen based on the second\nargument to\nDES_ENCRYPT(), if one was given. With no argument, the first\nkey from\nthe DES key file is used. With a key_num argument, the given\nkey \nnumber (0-9) from the DES key file is used. With a key_str\nargument,\nthe given key string is used to encrypt str. \n \nThe key file can be specified with the --des-key-file server\noption.\n \nThe return string is a binary string where the first\ncharacter is \nCHAR(128 | key_num). If an error occurs, DES_ENCRYPT()\nreturns NULL.\n \nThe 128 is added to make it easier to recognize an encrypted\nkey. If\nyou use a string key, key_num is 127.\n \nThe string length for the result is given by this formula:\n \nnew_len = orig_len + (8 - (orig_len % 8)) + 1\n \nEach line in the DES key file has the following format:\n \nkey_num des_key_str\n \nEach key_num value must be a number in the range from 0 to\n9. Lines in\nthe file may be in any order. des_key_str is the string that\nis used\nto encrypt the message. There should be at least one space\nbetween the\nnumber and the key. The first key is the default key that is\nused if\nyou do not specify any key argument to DES_ENCRYPT().\n \nYou can tell MariaDB to read new key values from the key\nfile with the\nFLUSH DES_KEY_FILE statement. This requires the RELOAD\nprivilege.\n \nOne benefit of having a set of default keys is that it gives\napplications a way to check for the existence of encrypted\ncolumn\nvalues, without giving the end user the right to decrypt\nthose values.\n \n\nSELECT customer_address FROM customer_table \n WHERE crypted_credit_card =\nDES_ENCRYPT(''credit_card_number''); +description=Encrypts the string with the given key using the Triple-DES algorithm.\n\nThis function works only if MariaDB has been configured with TLS support.\n\nThe encryption key to use is chosen based on the second argument to\nDES_ENCRYPT(), if one was given. With no argument, the first key from the DES\nkey file is used. With a key_num argument, the given key number (0-9) from the\nDES key file is used. With a key_str argument, the given key string is used to\nencrypt str.\n\nThe key file can be specified with the --des-key-file server option.\n\nThe return string is a binary string where the first character is CHAR(128 |\nkey_num). If an error occurs, DES_ENCRYPT() returns NULL.\n\nThe 128 is added to make it easier to recognize an encrypted key. If you use a\nstring key, key_num is 127.\n\nThe string length for the result is given by this formula:\n\nnew_len = orig_len + (8 - (orig_len % 8)) + 1\n\nEach line in the DES key file has the following format:\n\nkey_num des_key_str\n\nEach key_num value must be a number in the range from 0 to 9. Lines in the\nfile may be in any order. des_key_str is the string that is used to encrypt\nthe message. There should be at least one space between the number and the\nkey. The first key is the default key that is used if you do not specify any\nkey argument to DES_ENCRYPT().\n\nYou can tell MariaDB to read new key values from the key file with the FLUSH\nDES_KEY_FILE statement. This requires the RELOAD privilege.\n\nOne benefit of having a set of default keys is that it gives applications a\nway to check for the existence of encrypted column values, without giving the\nend user the right to decrypt those values.\n\nExamples\n--------\n\nSELECT customer_address FROM customer_table \n WHERE crypted_credit_card = DES_ENCRYPT('credit_card_number');\n\nURL: https://mariadb.com/kb/en/des_encrypt/ [DISJOINT] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether g1 is spatially disjoint\nfrom\n(does not intersect) g2.\n \nDISJOINT() tests the opposite relationship to INTERSECTS().\n \nDISJOINT() is based on the original MySQL implementation and\nuses object bounding rectangles, while ST_DISJOINT() uses\nobject shapes. +description=Returns 1 or 0 to indicate whether g1 is spatially disjoint from (does not\nintersect) g2.\n\nDISJOINT() tests the opposite relationship to INTERSECTS().\n\nDISJOINT() is based on the original MySQL implementation and uses object\nbounding rectangles, while ST_DISJOINT() uses object shapes.\n\nURL: https://mariadb.com/kb/en/disjoint/ +[DOUBLE] +declaration=M,D +category=Data Types +description=A normal-size (double-precision) floating-point number (see FLOAT for a\nsingle-precision floating-point number).\n\nAllowable values are:\n\n* -1.7976931348623157E+308 to -2.2250738585072014E-308\n* 0\n* 2.2250738585072014E-308 to 1.7976931348623157E+308\n\nThese are the theoretical limits, based on the IEEE standard. The actual range\nmight be slightly smaller depending on your hardware or operating system.\n\nM is the total number of digits and D is the number of digits following the\ndecimal point. If M and D are omitted, values are stored to the limits allowed\nby the hardware. A double-precision floating-point number is accurate to\napproximately 15 decimal places.\n\nUNSIGNED, if specified, disallows negative values.\n\nZEROFILL, if specified, pads the number with zeros, up to the total number of\ndigits specified by M.\n\nREAL and DOUBLE PRECISION are synonyms, unless the REAL_AS_FLOAT SQL mode is\nenabled, in which case REAL is a synonym for FLOAT rather than DOUBLE.\n\nSee Floating Point Accuracy for issues when using floating-point numbers.\n\nFor more details on the attributes, see Numeric Data Type Overview.\n\nExamples\n--------\n\nCREATE TABLE t1 (d DOUBLE(5,0) zerofill);\n\nINSERT INTO t1 VALUES (1),(2),(3),(4);\n\nSELECT * FROM t1;\n+-------+\n| d |\n+-------+\n| 00001 |\n| 00002 |\n| 00003 |\n| 00004 |\n+-------+\n\nURL: https://mariadb.com/kb/en/double/ [ELT] declaration=N, str1[, str2, str3,...] category=String Functions -description=Takes a numeric argument and a series of string arguments.\nReturns the string that corresponds to the given numeric\nposition. For instance, it returns str1 if N is 1, str2 if N\nis 2, and so on. If the numeric argument is a FLOAT, MariaDB\nrounds it to the nearest INTEGER. If the numeric argument is\nless than 1, greater than the total number of arguments, or\nnot a number, ELT() returns NULL. It must have at least two\narguments.\n \nIt is complementary to the FIELD() function.\n \n\nSELECT ELT(1, ''ej'', ''Heja'', ''hej'', ''foo'');\n+------------------------------------+\n| ELT(1, ''ej'', ''Heja'', ''hej'', ''foo'') |\n+------------------------------------+\n| ej |\n+------------------------------------+\n \nSELECT ELT(4, ''ej'', ''Heja'', ''hej'', ''foo'');\n+------------------------------------+\n| ELT(4, ''ej'', ''Heja'', ''hej'', ''foo'') |\n+------------------------------------+\n| foo |\n+------------------------------------+ +description=Takes a numeric argument and a series of string arguments. Returns the string\nthat corresponds to the given numeric position. For instance, it returns str1\nif N is 1, str2 if N is 2, and so on. If the numeric argument is a FLOAT,\nMariaDB rounds it to the nearest INTEGER. If the numeric argument is less than\n1, greater than the total number of arguments, or not a number, ELT() returns\nNULL. It must have at least two arguments.\n\nIt is complementary to the FIELD() function.\n\nExamples\n--------\n\nSELECT ELT(1, 'ej', 'Heja', 'hej', 'foo');\n+------------------------------------+\n| ELT(1, 'ej', 'Heja', 'hej', 'foo') |\n+------------------------------------+\n| ej |\n+------------------------------------+\n\nSELECT ELT(4, 'ej', 'Heja', 'hej', 'foo');\n+------------------------------------+\n| ELT(4, 'ej', 'Heja', 'hej', 'foo') |\n+------------------------------------+\n| foo |\n+------------------------------------+\n\nURL: https://mariadb.com/kb/en/elt/ [ENCODE] declaration=str,pass_str category=Encryption Functions -description=ENCODE is not considered cryptographically secure, and\nshould not be used for password encryption.\n \nEncrypt str using pass_str as the password. To decrypt the\nresult, use\nDECODE().\n \nThe result is a binary string of the same length as str.\n \nThe strength of the encryption is based on how good the\nrandom generator is. \n \nIt is not recommended to rely on the encryption performed by\nthe ENCODE function. Using a salt value (changed when a\npassword is updated) will improve matters somewhat, but for\nstoring passwords, consider a more cryptographically secure\nfunction, such as SHA2().\n \n\nENCODE(''not so secret text'',\nCONCAT(''random_salt'',''password'')) +description=ENCODE is not considered cryptographically secure, and should not be used for\npassword encryption.\n\nEncrypt str using pass_str as the password. To decrypt the result, use\nDECODE().\n\nThe result is a binary string of the same length as str.\n\nThe strength of the encryption is based on how good the random generator is.\n\nIt is not recommended to rely on the encryption performed by the ENCODE\nfunction. Using a salt value (changed when a password is updated) will improve\nmatters somewhat, but for storing passwords, consider a more cryptographically\nsecure function, such as SHA2().\n\nExamples\n--------\n\nENCODE('not so secret text', CONCAT('random_salt','password'))\n\nURL: https://mariadb.com/kb/en/encode/ [ENCRYPT] declaration=str[,salt] category=Encryption Functions -description=Encrypts a string using the Unix crypt() system call,\nreturning an encrypted binary string. The salt argument\nshould be a string with at least two characters or the\nreturned result will be NULL. If no salt argument is given,\na random value of sufficient length is used.\n \nIt is not recommended to use ENCRYPT() with utf16, utf32 or\nucs2 multi-byte character sets because the crypt() system\ncall expects a string terminated with a zero byte.\n \nNote that the underlying crypt() system call may have some\nlimitations, such as ignoring all but the first eight\ncharacters.\n \nIf the have_crypt system variable is set to NO (because the\ncrypt() system call is not available), the ENCRYPT function\nwill always return NULL.\n \n\nSELECT ENCRYPT(''encrypt me'');\n+-----------------------+\n| ENCRYPT(''encrypt me'') |\n+-----------------------+\n| 4I5BsEx0lqTDk |\n+-----------------------+ +description=Encrypts a string using the Unix crypt() system call, returning an encrypted\nbinary string. The salt argument should be a string with at least two\ncharacters or the returned result will be NULL. If no salt argument is given,\na random value of sufficient length is used.\n\nIt is not recommended to use ENCRYPT() with utf16, utf32 or ucs2 multi-byte\ncharacter sets because the crypt() system call expects a string terminated\nwith a zero byte.\n\nNote that the underlying crypt() system call may have some limitations, such\nas ignoring all but the first eight characters.\n\nIf the have_crypt system variable is set to NO (because the crypt() system\ncall is not available), the ENCRYPT function will always return NULL.\n\nExamples\n--------\n\nSELECT ENCRYPT('encrypt me');\n+-----------------------+\n| ENCRYPT('encrypt me') |\n+-----------------------+\n| 4I5BsEx0lqTDk |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/encrypt/ [ENUM] -declaration=''value1'',''value2'',... +declaration='value1','value2',... category=Data Types -description=An enumeration. A string object that can have only one\nvalue, chosen\nfrom the list of values ''value1'', ''value2'', ..., NULL or\nthe special \n'''' error value. In theory, an ENUM column can have a\nmaximum of 65,535 distinct\nvalues; in practice, the real maximum depends on many\nfactors. ENUM values are represented internally as integers.\n \nTrailing spaces are automatically stripped from ENUM values\non table creation.\n \nENUMs require relatively little storage space compared to\nstrings, either one or two bytes depending on the number of\nenumeration values.\n \nNULL and empty values\n \nAn ENUM can also contain NULL and empty values. If the ENUM\ncolumn is declared to permit NULL values, NULL becomes a\nvalid value, as well as the default value (see below). If\nstrict SQL Mode is not enabled, and an invalid value is\ninserted into an ENUM, a special empty string, with an index\nvalue of zero (see Numeric index, below), is inserted, with\na warning. This may be confusing, because the empty string\nis also a possible value, and the only difference if that in\nthis case its index is not 0. Inserting will fail with an\nerror if strict mode is active.\n \nIf a DEFAULT clause is missing, the default value will be:\nNULL is the column is nullable;\notherwise, the first value in the enumaration.\n \nNumeric index\n \nENUM values are indexed numerically in the order they are\ndefined, and sorting will be performed in this numeric\norder. We suggest not using ENUM to store numerals, as there\nis little to no storage space benefit, and it is easy to\nconfuse the enum integer with the enum numeral value by\nleaving out the quotes.\n \nAn ENUM defined as ENUM(''apple'',''orange'',''pear'') would\nhave the following index values:\n \nIndex | Value | \n \nNULL | NULL | \n \n0 | '''' | \n \n1 | ''apple'' | \n \n2 | ''orange'' | \n \n3 | ''pear'' | \n \n\nCREATE TABLE fruits (\n id INT NOT NULL auto_increment PRIMARY KEY,\n fruit ENUM(''apple'',''orange'',''pear''),\n bushels INT);\n \nDESCRIBE fruits;\n \n+---------+-------------------------------+------+-----+---------+----------------+\n| Field | Type | Null | Key | Default | Extra |\n+---------+-------------------------------+------+-----+---------+----------------+\n| id | int(11) | NO | PRI | NULL | auto_increment |\n| fruit | enum(''apple'',''orange'',''pear'') | YES | | NULL\n| |\n| bushels | int(11) | YES | | NULL | |\n+---------+-------------------------------+------+-----+---------+----------------+\n \nINSERT INTO fruits\n (fruit,bushels) VALUES\n (''pear'',20),\n (''apple'',100),\n (''orange'',25);\n \nINSERT INTO fruits\n (fruit,bushels) VALUES\n (''avocado'',10);\nERROR 1265 (01000): Data truncated for column ''fruit'' at\nrow 1\n \nSELECT * FROM fruits;\n \n+----+--------+---------+\n| id | fruit | bushels |\n+----+--------+---------+\n| 1 | pear | 20 |\n| 2 | apple | 100 |\n| 3 | orange | 25 |\n+----+--------+---------+\n \nSelecting by numeric index:\n \nSELECT * FROM fruits WHERE fruit=2;\n \n+----+--------+---------+\n| id | fruit | bushels |\n+----+--------+---------+\n| 3 | orange | 25 |\n+----+--------+---------+\n \nSorting is according to the index value:\n \nCREATE TABLE enums (a ENUM(''2'',''1''));\n \nINSERT INTO enums VALUES (''1''),(''2'');\n \nSELECT * FROM enums ORDER BY a ASC;\n \n+------+\n| a |\n+------+\n| 2 |\n| 1 |\n+------+\n \nIt''s easy to get confused between returning the enum\ninteger with the stored value, so we don''t suggest using\nENUM to store numerals. The first example returns the 1st\nindexed field (''2'' has an index value of 1, as it''s\ndefined first), while the second example returns the string\nvalue ''1''.\n \nSELECT * FROM enums WHERE a=1;\n \n+------+\n| a |\n+------+\n| 2 |\n+------+\n \nSELECT * FROM enums WHERE a=''1'';\n \n+------+\n| a |\n+------+\n| 1 |\n+------+ +description=An enumeration. A string object that can have only one value, chosen from the\nlist of values 'value1', 'value2', ..., NULL or the special '' error value. In\ntheory, an ENUM column can have a maximum of 65,535 distinct values; in\npractice, the real maximum depends on many factors. ENUM values are\nrepresented internally as integers.\n\nTrailing spaces are automatically stripped from ENUM values on table creation.\n\nENUMs require relatively little storage space compared to strings, either one\nor two bytes depending on the number of enumeration values.\n\nNULL and empty values\n---------------------\n\nAn ENUM can also contain NULL and empty values. If the ENUM column is declared\nto permit NULL values, NULL becomes a valid value, as well as the default\nvalue (see below). If strict SQL Mode is not enabled, and an invalid value is\ninserted into an ENUM, a special empty string, with an index value of zero\n(see Numeric index, below), is inserted, with a warning. This may be\nconfusing, because the empty string is also a possible value, and the only\ndifference if that is this case its index is not 0. Inserting will fail with\nan error if strict mode is active.\n\nIf a DEFAULT clause is missing, the default value will be:\n\n* NULL if the column is nullable;\n* otherwise, the first value in the enumeration.\n\nNumeric index\n-------------\n\nENUM values are indexed numerically in the order they are defined, and sorting\nwill be performed in this numeric order. We suggest not using ENUM to store\nnumerals, as there is little to no storage space benefit, and it is easy to\nconfuse the enum integer with the enum numeral value by leaving out the quotes.\n\nAn ENUM defined as ENUM('apple','orange','pear') would have the following\nindex values:\n\n+--------------------------------------+--------------------------------------+\n| Index | Value |\n+--------------------------------------+--------------------------------------+\n| NULL | NULL |\n+--------------------------------------+--------------------------------------+\n| 0 | '' |\n+--------------------------------------+--------------------------------------+\n| 1 | 'apple' |\n+--------------------------------------+--------------------------------------+\n| 2 | 'orange' |\n+--------------------------------------+--------------------------------------+\n ... [EQUALS] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether g1 is spatially equal to\ng2.\n \nEQUALS() is based on the original MySQL implementation and\nuses object bounding rectangles, while ST_EQUALS() uses\nobject shapes.\n \nFrom MariaDB 10.2.3, MBREQUALS is a synonym for Equals. -[EXPORT_SET] -declaration=bits, on, off[, separator[, number_of_bits]] -category=String Functions -description=Takes a minimum of three arguments. Returns a string where\neach bit in the given bits argument is returned, with the\nstring values given for on and off. \n \nBits are examined from right to left, (from low-order to\nhigh-order bits). Strings are added to the result from left\nto right, separated by a separator string (defaults as\n'',''). You can optionally limit the number of bits the\nEXPORT_SET() function examines using the number_of_bits\noption. \n \nIf any of the arguments are set as NULL, the function\nreturns NULL.\n \n\nSELECT EXPORT_SET(5,''Y'',''N'','','',4);\n+-----------------------------+\n| EXPORT_SET(5,''Y'',''N'','','',4) |\n+-----------------------------+\n| Y,N,Y,N |\n+-----------------------------+\n \nSELECT EXPORT_SET(6,''1'',''0'','','',10);\n+------------------------------+\n| EXPORT_SET(6,''1'',''0'','','',10) |\n+------------------------------+\n| 0,1,1,0,0,0,0,0,0,0 |\n+------------------------------+ +description=Returns 1 or 0 to indicate whether g1 is spatially equal to g2.\n\nEQUALS() is based on the original MySQL implementation and uses object\nbounding rectangles, while ST_EQUALS() uses object shapes.\n\nFrom MariaDB 10.2.3, MBREQUALS is a synonym for Equals.\n\nURL: https://mariadb.com/kb/en/equals/ +[EXCEPT] +declaration=SELECT c_name AS name, email FROM employees +category=Data Manipulation +description=Difference between UNION, EXCEPT and INTERSECT. INTERSECT ALL and EXCEPT ALL\nare available from MariaDB 10.5.0.\n\nCREATE TABLE seqs (i INT);\nINSERT INTO seqs VALUES (1),(2),(2),(3),(3),(4),(5),(6);\n\nSELECT i FROM seqs WHERE i <= 3 UNION SELECT i FROM seqs WHERE i>=3;\n+------+\n| i |\n+------+\n| 1 |\n| 2 |\n| 3 |\n| 4 |\n| 5 |\n| 6 |\n+------+\n\nSELECT i FROM seqs WHERE i <= 3 UNION ALL SELECT i FROM seqs WHERE i>=3;\n+------+\n| i |\n+------+\n| 1 |\n| 2 |\n| 2 |\n| 3 |\n| 3 |\n| 3 |\n| 3 |\n| 4 |\n| 5 |\n| 6 |\n+------+\n\nSELECT i FROM seqs WHERE i <= 3 EXCEPT SELECT i FROM seqs WHERE i>=3;\n+------+\n| i |\n+------+\n| 1 |\n| 2 |\n+------+\n\nSELECT i FROM seqs WHERE i <= 3 EXCEPT ALL SELECT i FROM seqs WHERE i>=3;\n+------+\n| i |\n+------+\n| 1 |\n| 2 |\n| 2 |\n+------+\n ... [EXP] declaration=X category=Numeric Functions -description=Returns the value of e (the base of natural logarithms)\nraised to the\npower of X. The inverse of this function is LOG() (using a\nsingle\nargument only) or LN().\n \nIf X is NULL, this function returns NULL.\n \n\nSELECT EXP(2);\n+------------------+\n| EXP(2) |\n+------------------+\n| 7.38905609893065 |\n+------------------+\n \nSELECT EXP(-2);\n+--------------------+\n| EXP(-2) |\n+--------------------+\n| 0.1353352832366127 |\n+--------------------+\n \nSELECT EXP(0);\n+--------+\n| EXP(0) |\n+--------+\n| 1 |\n+--------+\n \nSELECT EXP(NULL);\n+-----------+\n| EXP(NULL) |\n+-----------+\n| NULL |\n+-----------+ -[EXTRACTVALUE] -declaration=xml_frag, xpath_expr +description=Returns the value of e (the base of natural logarithms) raised to the power of\nX. The inverse of this function is LOG() (using a single argument only) or\nLN().\n\nIf X is NULL, this function returns NULL.\n\nExamples\n--------\n\nSELECT EXP(2);\n+------------------+\n| EXP(2) |\n+------------------+\n| 7.38905609893065 |\n+------------------+\n\nSELECT EXP(-2);\n+--------------------+\n| EXP(-2) |\n+--------------------+\n| 0.1353352832366127 |\n+--------------------+\n\nSELECT EXP(0);\n+--------+\n| EXP(0) |\n+--------+\n| 1 |\n+--------+\n\nSELECT EXP(NULL);\n+-----------+\n| EXP(NULL) |\n+-----------+\n| NULL |\n+-----------+\n\nURL: https://mariadb.com/kb/en/exp/ +[EXPORT_SET] +declaration=bits, on, off[, separator[, number_of_bits]] category=String Functions -description=The EXTRACTVALUE() function takes two string arguments: a\nfragment of XML markup and an XPath expression, (also known\nas a locator). It returns the text (That is, CDDATA), of the\nfirst text node which is a child of the element or elements\nmatching the XPath expression. \n \nIn cases where a valid XPath expression does not match any\ntext nodes in a valid XML fragment, (including the implicit\n/text() expression), the EXTRACTVALUE() function returns an\nempty string.\n \nInvalid Arguments\n \nWhen either the XML fragment or the XPath expression is\nNULL, the EXTRACTVALUE() function returns NULL. When the XML\nfragment is invalid, it raises a warning Code 1525:\n \nWarning (Code 1525): Incorrect XML value: ''parse error at\nline 1 pos 11: unexpected END-OF-INPUT''\n \nWhen the XPath value is invalid, it generates an Error 1105:\n \nERROR 1105 (HY000): XPATH syntax error: '')''\n \nExplicit text() Expressions\n \nThis function is the equivalent of performing a match using\nthe XPath expression after appending /text(). In other\nwords:\n \nSELECT\n EXTRACTVALUE(''example'', ''/cases/case'') AS ''Base\nExample'',\n EXTRACTVALUE(''example'', ''/cases/case/text()'') AS\n''text() Example'';\n \n+--------------+----------------+\n| Base Example | text() Example |\n+--------------+----------------+\n| example | example |\n+--------------+----------------+\n \nCount Matches\n \nWhen EXTRACTVALUE() returns multiple matches, it returns the\ncontent of the first child text node of each matching\nelement, in the matched order, as a single, space-delimited\nstring.\n \nBy design, the EXTRACTVALUE() function makes no distinction\nbetween a match on an empty element and no match at all. If\nyou need to determine whether no matching element was found\nin the XML fragment or if an element was found that\ncontained no child text nodes, use the XPath count()\nfunction. \n \nFor instance, when looking for a value that exists, but\ncontains no child text nodes, you would get a count of the\nnumber of matching instances:\n \nSELECT\n EXTRACTVALUE('''', ''/cases/case'') AS ''Empty Example'',\n EXTRACTVALUE('''', ''/cases/case/count()'') AS ''count()\nExample'';\n \n+---------------+-----------------+\n| Empty Example | count() Example |\n+---------------+-----------------+\n| | 1 |\n+---------------+-----------------+\n \nAlternatively, when looking for a value that doesn''t exist,\ncount() returns 0.\n \nSELECT\n EXTRACTVALUE('''', ''/cases/person'') AS ''No Match\nExample'',\n EXTRACTVALUE('''', ''/cases/person/count()'') AS ''count()\nExample'';\n \n+------------------+-----------------+\n| No Match Example | count() Example |\n+------------------+-----------------+\n| | 0|\n+------------------+-----------------+\n \nMatches\n \nImportant: The EXTRACTVALUE() function only returns CDDATA.\nIt does not return tags that the element might contain or\nthe text that these child elements contain.\n \nSELECT EXTRACTVALUE(''Personx@example.com'', ''/cases'') AS\nCase;\n \n+--------+\n| Case |\n+--------+\n| Person |\n+--------+\n \nNote, in the above example, while the XPath expression\nmatches to the parent instance, it does not return the\ncontained tag or its content.\n \n\nSELECT\n ExtractValue(''cccddd'', ''/a'') AS val1,\n ExtractValue(''cccddd'', ''/a/b'') AS val2,\n ExtractValue(''cccddd'', ''//b'') AS val3,\n ExtractValue(''cccddd'', ''/b'') AS val4,\n ExtractValue(''cccdddeee'', ''//b'') AS val5;\n \n+------+------+------+------+---------+\n| val1 | val2 | val3 | val4 | val5 |\n+------+------+------+------+---------+\n| ccc | ddd | ddd | | ddd eee |\n+------+------+------+------+---------+ +description=Takes a minimum of three arguments. Returns a string where each bit in the\ngiven bits argument is returned, with the string values given for on and off.\n\nBits are examined from right to left, (from low-order to high-order bits).\nStrings are added to the result from left to right, separated by a separator\nstring (defaults as ','). You can optionally limit the number of bits the\nEXPORT_SET() function examines using the number_of_bits option.\n\nIf any of the arguments are set as NULL, the function returns NULL.\n\nExamples\n--------\n\nSELECT EXPORT_SET(5,'Y','N',',',4);\n+-----------------------------+\n| EXPORT_SET(5,'Y','N',',',4) |\n+-----------------------------+\n| Y,N,Y,N |\n+-----------------------------+\n\nSELECT EXPORT_SET(6,'1','0',',',10);\n+------------------------------+\n| EXPORT_SET(6,'1','0',',',10) |\n+------------------------------+\n| 0,1,1,0,0,0,0,0,0,0 |\n+------------------------------+\n\nURL: https://mariadb.com/kb/en/export_set/ [EXTRACT] declaration=unit FROM date category=Date and Time Functions -description=The EXTRACT() function extracts the required unit from the\ndate. See Date and Time Units for a complete list of\npermitted units.\n \nIn MariaDB 10.0.7 and MariaDB 5.5.35, EXTRACT (HOUR FROM\n...) was changed to return a value from 0 to 23, adhering to\nthe SQL standard. Until MariaDB 10.0.6 and MariaDB 5.5.34,\nand in all versions of MySQL at least as of MySQL 5.7, it\ncould return a value > 23. HOUR() is not a standard\nfunction, so continues to adhere to the old behaviour\ninherited from MySQL.\n \n\nSELECT EXTRACT(YEAR FROM ''2009-07-02'');\n+---------------------------------+\n| EXTRACT(YEAR FROM ''2009-07-02'') |\n+---------------------------------+\n| 2009 |\n+---------------------------------+\n \nSELECT EXTRACT(YEAR_MONTH FROM ''2009-07-02 01:02:03'');\n+------------------------------------------------+\n| EXTRACT(YEAR_MONTH FROM ''2009-07-02 01:02:03'') |\n+------------------------------------------------+\n| 200907 |\n+------------------------------------------------+\n \nSELECT EXTRACT(DAY_MINUTE FROM ''2009-07-02 01:02:03'');\n+------------------------------------------------+\n| EXTRACT(DAY_MINUTE FROM ''2009-07-02 01:02:03'') |\n+------------------------------------------------+\n| 20102 |\n+------------------------------------------------+\n \nSELECT EXTRACT(MICROSECOND FROM ''2003-01-02\n10:30:00.000123'');\n+--------------------------------------------------------+\n| EXTRACT(MICROSECOND FROM ''2003-01-02 10:30:00.000123'') |\n+--------------------------------------------------------+\n| 123 |\n+--------------------------------------------------------+\n \nFrom MariaDB 10.0.7 and MariaDB 5.5.35, EXTRACT (HOUR\nFROM...) returns a value from 0 to 23, as per the SQL\nstandard. HOUR is not a standard function, so continues to\nadhere to the old behaviour inherited from MySQL.\n \nSELECT EXTRACT(HOUR FROM ''26:30:00''), HOUR(''26:30:00'');\n+-------------------------------+------------------+\n| EXTRACT(HOUR FROM ''26:30:00'') | HOUR(''26:30:00'') |\n+-------------------------------+------------------+\n| 2 | 26 |\n+-------------------------------+------------------+ +description=The EXTRACT() function extracts the required unit from the date. See Date and\nTime Units for a complete list of permitted units.\n\nIn MariaDB 10.0.7 and MariaDB 5.5.35, EXTRACT (HOUR FROM ...) was changed to\nreturn a value from 0 to 23, adhering to the SQL standard. Until MariaDB\n10.0.6 and MariaDB 5.5.34, and in all versions of MySQL at least as of MySQL\n5.7, it could return a value > 23. HOUR() is not a standard function, so\ncontinues to adhere to the old behaviour inherited from MySQL.\n\nExamples\n--------\n\nSELECT EXTRACT(YEAR FROM '2009-07-02');\n+---------------------------------+\n| EXTRACT(YEAR FROM '2009-07-02') |\n+---------------------------------+\n| 2009 |\n+---------------------------------+\n\nSELECT EXTRACT(YEAR_MONTH FROM '2009-07-02 01:02:03');\n+------------------------------------------------+\n| EXTRACT(YEAR_MONTH FROM '2009-07-02 01:02:03') |\n+------------------------------------------------+\n| 200907 |\n+------------------------------------------------+\n\nSELECT EXTRACT(DAY_MINUTE FROM '2009-07-02 01:02:03');\n+------------------------------------------------+\n| EXTRACT(DAY_MINUTE FROM '2009-07-02 01:02:03') |\n+------------------------------------------------+\n| 20102 |\n+------------------------------------------------+\n\nSELECT EXTRACT(MICROSECOND FROM '2003-01-02 10:30:00.000123');\n+--------------------------------------------------------+\n| EXTRACT(MICROSECOND FROM '2003-01-02 10:30:00.000123') |\n+--------------------------------------------------------+\n| 123 |\n+--------------------------------------------------------+\n\nFrom MariaDB 10.0.7 and MariaDB 5.5.35, EXTRACT (HOUR FROM...) returns a value\nfrom 0 to 23, as per the SQL standard. HOUR is not a standard function, so\ncontinues to adhere to the old behaviour inherited from MySQL.\n\nSELECT EXTRACT(HOUR FROM '26:30:00'), HOUR('26:30:00');\n+-------------------------------+------------------+\n| EXTRACT(HOUR FROM '26:30:00') | HOUR('26:30:00') |\n+-------------------------------+------------------+\n| 2 | 26 |\n+-------------------------------+------------------+\n\nURL: https://mariadb.com/kb/en/extract/ +[EXTRACTVALUE] +declaration=xml_frag, xpath_expr +category=String Functions +description=The EXTRACTVALUE() function takes two string arguments: a fragment of XML\nmarkup and an XPath expression, (also known as a locator). It returns the text\n(That is, CDDATA), of the first text node which is a child of the element or\nelements matching the XPath expression.\n\nIn cases where a valid XPath expression does not match any text nodes in a\nvalid XML fragment, (including the implicit /text() expression), the\nEXTRACTVALUE() function returns an empty string.\n\nInvalid Arguments\n-----------------\n\nWhen either the XML fragment or the XPath expression is NULL, the\nEXTRACTVALUE() function returns NULL. When the XML fragment is invalid, it\nraises a warning Code 1525:\n\nWarning (Code 1525): Incorrect XML value: 'parse error at line 1 pos 11:\nunexpected END-OF-INPUT'\n\nWhen the XPath value is invalid, it generates an Error 1105:\n\nERROR 1105 (HY000): XPATH syntax error: ')'\n\nExplicit text() Expressions\n---------------------------\n\nThis function is the equivalent of performing a match using the XPath\nexpression after appending /text(). In other words:\n\nSELECT\n EXTRACTVALUE('example', '/cases/case')\n AS 'Base Example',\n EXTRACTVALUE('example', '/cases/case/text()')\n AS 'text() Example';\n+--------------+----------------+\n| Base Example | text() Example |\n+--------------+----------------+\n| example | example |\n+--------------+----------------+\n\nCount Matches\n-------------\n\nWhen EXTRACTVALUE() returns multiple matches, it returns the content of the\nfirst child text node of each matching element, in the matched order, as a\nsingle, space-delimited string.\n\nBy design, the EXTRACTVALUE() function makes no distinction between a match on\nan empty element and no match at all. If you need to determine whether no\nmatching element was found in the XML fragment or if an element was found that\n ... [FIELD] declaration=pattern, str1[,str2,...] category=String Functions -description=Returns the index position of the string or number matching\nthe given pattern. Returns 0 in the event that none of the\narguments match the pattern. Raises an Error 1582 if not\ngiven at least two arguments.\n \nWhen all arguments given to the FIELD() function are\nstrings, they are treated as case-insensitive. When all the\narguments are numbers, they are treated as numbers.\nOtherwise, they are treated as doubles. \n \nIf the given pattern occurs more than once, the FIELD()\nfunction only returns the index of the first instance. If\nthe given pattern is NULL, the function returns 0, as a NULL\npattern always fails to match.\n \nThis function is complementary to the ELT() function.\n \n\nSELECT FIELD(''ej'', ''Hej'', ''ej'', ''Heja'', ''hej'',\n''foo'') \n AS ''Field Results'';\n \n+---------------+\n| Field Results | \n+---------------+\n| 2 |\n+---------------+\n \nSELECT FIELD(''fo'', ''Hej'', ''ej'', ''Heja'', ''hej'',\n''foo'')\n AS ''Field Results'';\n \n+---------------+\n| Field Results | \n+---------------+\n| 0 |\n+---------------+\n \nSELECT FIELD(1, 2, 3, 4, 5, 1) AS ''Field Results'';\n \n+---------------+\n| Field Results |\n+---------------+\n| 5 |\n+---------------+\n \nSELECT FIELD(NULL, 2, 3) AS ''Field Results'';\n \n+---------------+\n| Field Results |\n+---------------+\n| 0 |\n+---------------+\n \nSELECT FIELD(''fail'') AS ''Field Results'';\n \nError 1582 (42000): Incorrect parameter count in call\nto native function ''field'' +description=Returns the index position of the string or number matching the given pattern.\nReturns 0 in the event that none of the arguments match the pattern. Raises an\nError 1582 if not given at least two arguments.\n\nWhen all arguments given to the FIELD() function are strings, they are treated\nas case-insensitive. When all the arguments are numbers, they are treated as\nnumbers. Otherwise, they are treated as doubles.\n\nIf the given pattern occurs more than once, the FIELD() function only returns\nthe index of the first instance. If the given pattern is NULL, the function\nreturns 0, as a NULL pattern always fails to match.\n\nThis function is complementary to the ELT() function.\n\nExamples\n--------\n\nSELECT FIELD('ej', 'Hej', 'ej', 'Heja', 'hej', 'foo') \n AS 'Field Results';\n+---------------+\n| Field Results | \n+---------------+\n| 2 |\n+---------------+\n\nSELECT FIELD('fo', 'Hej', 'ej', 'Heja', 'hej', 'foo')\n AS 'Field Results';\n+---------------+\n| Field Results | \n+---------------+\n| 0 |\n+---------------+\n\nSELECT FIELD(1, 2, 3, 4, 5, 1) AS 'Field Results';\n+---------------+\n| Field Results |\n+---------------+\n| 5 |\n+---------------+\n\nSELECT FIELD(NULL, 2, 3) AS 'Field Results';\n+---------------+\n| Field Results |\n+---------------+\n| 0 |\n+---------------+\n\nSELECT FIELD('fail') AS 'Field Results';\nError 1582 (42000): Incorrect parameter count in call\nto native function 'field'\n\nURL: https://mariadb.com/kb/en/field/ [FIND_IN_SET] declaration=pattern, strlist category=String Functions -description=Returns the index position where the given pattern occurs in\na string list. The first argument is the pattern you want to\nsearch for. The second argument is a string containing\ncomma-separated variables. If the second argument is of the\nSET data-type, the function is optimized to use bit\narithmetic.\n \nIf the pattern does not occur in the string list or if the\nstring list is an empty string, the function returns 0. If\neither argument is NULL, the function returns NULL. The\nfunction does not return the correct result if the pattern\ncontains a comma (",") character.\n \n\nSELECT FIND_IN_SET(''b'',''a,b,c,d'') AS "Found Results";\n \n+---------------+\n| Found Results |\n+---------------+\n| 2 |\n+---------------+ -[FIRST_VALUE1] -name=FIRST_VALUE +description=Returns the index position where the given pattern occurs in a string list.\nThe first argument is the pattern you want to search for. The second argument\nis a string containing comma-separated variables. If the second argument is of\nthe SET data-type, the function is optimized to use bit arithmetic.\n\nIf the pattern does not occur in the string list or if the string list is an\nempty string, the function returns 0. If either argument is NULL, the function\nreturns NULL. The function does not return the correct result if the pattern\ncontains a comma (",") character.\n\nExamples\n--------\n\nSELECT FIND_IN_SET('b','a,b,c,d') AS "Found Results";\n+---------------+\n| Found Results |\n+---------------+\n| 2 |\n+---------------+\n\nURL: https://mariadb.com/kb/en/find_in_set/ +[FIRST_VALUE] declaration=expr category=Window Functions -description=FIRST_VALUE returns the first result from an ordered set, or\nNULL if no such result exists.\n \n\nCREATE TABLE t1 (\n pk int primary key,\n a int,\n b int,\n c char(10),\n d decimal(10, 3),\n e real\n);\n \nINSERT INTO t1 VALUES\n( 1, 0, 1, ''one'', 0.1, 0.001),\n( 2, 0, 2, ''two'', 0.2, 0.002),\n( 3, 0, 3, ''three'', 0.3, 0.003),\n( 4, 1, 2, ''three'', 0.4, 0.004),\n( 5, 1, 1, ''two'', 0.5, 0.005),\n( 6, 1, 1, ''one'', 0.6, 0.006),\n( 7, 2, NULL, ''n_one'', 0.5, 0.007),\n( 8, 2, 1, ''n_two'', NULL, 0.008),\n( 9, 2, 2, NULL, 0.7, 0.009),\n(10, 2, 0, ''n_four'', 0.8, 0.010),\n(11, 2, 10, NULL, 0.9, NULL);\n \nSELECT pk, FIRST_VALUE(pk) OVER (ORDER BY pk) AS first_asc,\n LAST_VALUE(pk) OVER (ORDER BY pk) AS last_asc,\n FIRST_VALUE(pk) OVER (ORDER BY pk DESC) AS first_desc,\n LAST_VALUE(pk) OVER (ORDER BY pk DESC) AS last_desc\nFROM t1\nORDER BY pk DESC;\n \n+----+-----------+----------+------------+-----------+\n| pk | first_asc | last_asc | first_desc | last_desc |\n+----+-----------+----------+------------+-----------+\n| 11 | 1 | 11 | 11 | 11 |\n| 10 | 1 | 10 | 11 | 10 |\n| 9 | 1 | 9 | 11 | 9 |\n| 8 | 1 | 8 | 11 | 8 |\n| 7 | 1 | 7 | 11 | 7 |\n| 6 | 1 | 6 | 11 | 6 |\n| 5 | 1 | 5 | 11 | 5 |\n| 4 | 1 | 4 | 11 | 4 |\n| 3 | 1 | 3 | 11 | 3 |\n| 2 | 1 | 2 | 11 | 2 |\n| 1 | 1 | 1 | 11 | 1 |\n+----+-----------+----------+------------+-----------+\n \nCREATE OR REPLACE TABLE t1 (i int);\nINSERT INTO t1 VALUES\n(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);\n \nSELECT i,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW\nand 1 FOLLOWING) AS f_1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW and\n1 FOLLOWING) AS l_1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING\nAND 1 FOLLOWING) AS f_1p1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND\n1 FOLLOWING) AS f_1p1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING\nAND 1 PRECEDING) AS f_2p1p,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING AND\n1 PRECEDING) AS f_2p1p,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING\nAND 2 FOLLOWING) AS f_1f2f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING AND\n2 FOLLOWING) AS f_1f2f\nFROM t1;\n \n+------+------+------+--------+--------+--------+--------+--------+--------+\n| i | f_1f | l_1f | f_1p1f | f_1p1f | f_2p1p | f_2p1p |\nf_1f2f | f_1f2f |\n+------+------+------+--------+--------+--------+--------+--------+--------+\n| 1 | 1 | 2 | 1 | 2 | NULL | NULL | 2 | 3 |\n| 2 | 2 | 3 | 1 | 3 | 1 | 1 | 3 | 4 |\n| 3 | 3 | 4 | 2 | 4 | 1 | 2 | 4 | 5 |\n| 4 | 4 | 5 | 3 | 5 | 2 | 3 | 5 | 6 |\n| 5 | 5 | 6 | 4 | 6 | 3 | 4 | 6 | 7 |\n| 6 | 6 | 7 | 5 | 7 | 4 | 5 | 7 | 8 |\n| 7 | 7 | 8 | 6 | 8 | 5 | 6 | 8 | 9 |\n| 8 | 8 | 9 | 7 | 9 | 6 | 7 | 9 | 10 |\n| 9 | 9 | 10 | 8 | 10 | 7 | 8 | 10 | 10 |\n| 10 | 10 | 10 | 9 | 10 | 8 | 9 | NULL | NULL |\n+------+------+------+--------+--------+--------+--------+--------+--------+ -[FIRST_VALUE2] -name=FIRST_VALUE -declaration=[ PARTITION BY partition_expression ] [ ORDER BY order_list ] -category=Window Functions -description=FIRST_VALUE returns the first result from an ordered set, or\nNULL if no such result exists.\n \n\nCREATE TABLE t1 (\n pk int primary key,\n a int,\n b int,\n c char(10),\n d decimal(10, 3),\n e real\n);\n \nINSERT INTO t1 VALUES\n( 1, 0, 1, ''one'', 0.1, 0.001),\n( 2, 0, 2, ''two'', 0.2, 0.002),\n( 3, 0, 3, ''three'', 0.3, 0.003),\n( 4, 1, 2, ''three'', 0.4, 0.004),\n( 5, 1, 1, ''two'', 0.5, 0.005),\n( 6, 1, 1, ''one'', 0.6, 0.006),\n( 7, 2, NULL, ''n_one'', 0.5, 0.007),\n( 8, 2, 1, ''n_two'', NULL, 0.008),\n( 9, 2, 2, NULL, 0.7, 0.009),\n(10, 2, 0, ''n_four'', 0.8, 0.010),\n(11, 2, 10, NULL, 0.9, NULL);\n \nSELECT pk, FIRST_VALUE(pk) OVER (ORDER BY pk) AS first_asc,\n LAST_VALUE(pk) OVER (ORDER BY pk) AS last_asc,\n FIRST_VALUE(pk) OVER (ORDER BY pk DESC) AS first_desc,\n LAST_VALUE(pk) OVER (ORDER BY pk DESC) AS last_desc\nFROM t1\nORDER BY pk DESC;\n \n+----+-----------+----------+------------+-----------+\n| pk | first_asc | last_asc | first_desc | last_desc |\n+----+-----------+----------+------------+-----------+\n| 11 | 1 | 11 | 11 | 11 |\n| 10 | 1 | 10 | 11 | 10 |\n| 9 | 1 | 9 | 11 | 9 |\n| 8 | 1 | 8 | 11 | 8 |\n| 7 | 1 | 7 | 11 | 7 |\n| 6 | 1 | 6 | 11 | 6 |\n| 5 | 1 | 5 | 11 | 5 |\n| 4 | 1 | 4 | 11 | 4 |\n| 3 | 1 | 3 | 11 | 3 |\n| 2 | 1 | 2 | 11 | 2 |\n| 1 | 1 | 1 | 11 | 1 |\n+----+-----------+----------+------------+-----------+\n \nCREATE OR REPLACE TABLE t1 (i int);\nINSERT INTO t1 VALUES\n(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);\n \nSELECT i,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW\nand 1 FOLLOWING) AS f_1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW and\n1 FOLLOWING) AS l_1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING\nAND 1 FOLLOWING) AS f_1p1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND\n1 FOLLOWING) AS f_1p1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING\nAND 1 PRECEDING) AS f_2p1p,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING AND\n1 PRECEDING) AS f_2p1p,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING\nAND 2 FOLLOWING) AS f_1f2f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING AND\n2 FOLLOWING) AS f_1f2f\nFROM t1;\n \n+------+------+------+--------+--------+--------+--------+--------+--------+\n| i | f_1f | l_1f | f_1p1f | f_1p1f | f_2p1p | f_2p1p |\nf_1f2f | f_1f2f |\n+------+------+------+--------+--------+--------+--------+--------+--------+\n| 1 | 1 | 2 | 1 | 2 | NULL | NULL | 2 | 3 |\n| 2 | 2 | 3 | 1 | 3 | 1 | 1 | 3 | 4 |\n| 3 | 3 | 4 | 2 | 4 | 1 | 2 | 4 | 5 |\n| 4 | 4 | 5 | 3 | 5 | 2 | 3 | 5 | 6 |\n| 5 | 5 | 6 | 4 | 6 | 3 | 4 | 6 | 7 |\n| 6 | 6 | 7 | 5 | 7 | 4 | 5 | 7 | 8 |\n| 7 | 7 | 8 | 6 | 8 | 5 | 6 | 8 | 9 |\n| 8 | 8 | 9 | 7 | 9 | 6 | 7 | 9 | 10 |\n| 9 | 9 | 10 | 8 | 10 | 7 | 8 | 10 | 10 |\n| 10 | 10 | 10 | 9 | 10 | 8 | 9 | NULL | NULL |\n+------+------+------+--------+--------+--------+--------+--------+--------+ +description=FIRST_VALUE returns the first result from an ordered set, or NULL if no such\nresult exists.\n\nExamples\n--------\n\nCREATE TABLE t1 (\n pk int primary key,\n a int,\n b int,\n c char(10),\n d decimal(10, 3),\n e real\n);\n\nINSERT INTO t1 VALUES\n( 1, 0, 1, 'one', 0.1, 0.001),\n( 2, 0, 2, 'two', 0.2, 0.002),\n( 3, 0, 3, 'three', 0.3, 0.003),\n( 4, 1, 2, 'three', 0.4, 0.004),\n( 5, 1, 1, 'two', 0.5, 0.005),\n( 6, 1, 1, 'one', 0.6, 0.006),\n( 7, 2, NULL, 'n_one', 0.5, 0.007),\n( 8, 2, 1, 'n_two', NULL, 0.008),\n( 9, 2, 2, NULL, 0.7, 0.009),\n(10, 2, 0, 'n_four', 0.8, 0.010),\n(11, 2, 10, NULL, 0.9, NULL);\n\nSELECT pk, FIRST_VALUE(pk) OVER (ORDER BY pk) AS first_asc,\n LAST_VALUE(pk) OVER (ORDER BY pk) AS last_asc,\n FIRST_VALUE(pk) OVER (ORDER BY pk DESC) AS first_desc,\n LAST_VALUE(pk) OVER (ORDER BY pk DESC) AS last_desc\nFROM t1\nORDER BY pk DESC;\n\n+----+-----------+----------+------------+-----------+\n| pk | first_asc | last_asc | first_desc | last_desc |\n+----+-----------+----------+------------+-----------+\n| 11 | 1 | 11 | 11 | 11 |\n| 10 | 1 | 10 | 11 | 10 |\n| 9 | 1 | 9 | 11 | 9 |\n| 8 | 1 | 8 | 11 | 8 |\n| 7 | 1 | 7 | 11 | 7 |\n| 6 | 1 | 6 | 11 | 6 |\n| 5 | 1 | 5 | 11 | 5 |\n| 4 | 1 | 4 | 11 | 4 |\n| 3 | 1 | 3 | 11 | 3 |\n| 2 | 1 | 2 | 11 | 2 |\n| 1 | 1 | 1 | 11 | 1 |\n+----+-----------+----------+------------+-----------+\n ... +[FLOAT] +declaration=M,D +category=Data Types +description=A small (single-precision) floating-point number (see DOUBLE for a\nregular-size floating point number). Allowable values are:\n\n* -3.402823466E+38 to -1.175494351E-38\n* 0\n* 1.175494351E-38 to 3.402823466E+38.\n\nThese are the theoretical limits, based on the IEEE standard. The actual range\nmight be slightly smaller depending on your hardware or operating system.\n\nM is the total number of digits and D is the number of digits following the\ndecimal point. If M and D are omitted, values are stored to the limits allowed\nby the hardware. A single-precision floating-point number is accurate to\napproximately 7 decimal places.\n\nUNSIGNED, if specified, disallows negative values.\n\nUsing FLOAT might give you some unexpected problems because all calculations\nin MariaDB are done with double precision. See Floating Point Accuracy.\n\nFor more details on the attributes, see Numeric Data Type Overview.\n\nURL: https://mariadb.com/kb/en/float/ [FLOOR] declaration=X category=Numeric Functions -description=Returns the largest integer value not greater than X.\n \n\nSELECT FLOOR(1.23);\n+-------------+\n| FLOOR(1.23) |\n+-------------+\n| 1 |\n+-------------+\n \nSELECT FLOOR(-1.23);\n+--------------+\n| FLOOR(-1.23) |\n+--------------+\n| -2 |\n+--------------+ +description=Returns the largest integer value not greater than X.\n\nExamples\n--------\n\nSELECT FLOOR(1.23);\n+-------------+\n| FLOOR(1.23) |\n+-------------+\n| 1 |\n+-------------+\n\nSELECT FLOOR(-1.23);\n+--------------+\n| FLOOR(-1.23) |\n+--------------+\n| -2 |\n+--------------+\n\nURL: https://mariadb.com/kb/en/floor/ [FORMAT] declaration=num, decimal_position[, locale] category=String Functions -description=Formats the given number for display as a string, adding\nseparators to appropriate position and rounding the results\nto the given decimal position. For instance, it would format\n15233.345 to 15,233.35.\n \nIf the given decimal position is 0, it rounds to return no\ndecimal point or fractional part. You can optionally specify\na locale value to format numbers to the pattern appropriate\nfor the given region.\n \n\nSELECT FORMAT(1234567890.09876543210, 4) AS ''Format'';\n \n+--------------------+\n| Format |\n+--------------------+\n| 1,234,567,890.0988 |\n+--------------------+\n \nSELECT FORMAT(1234567.89, 4) AS ''Format'';\n \n+----------------+\n| Format |\n+----------------+\n| 1,234,567.8900 |\n+----------------+\n \nSELECT FORMAT(1234567.89, 0) AS ''Format'';\n \n+-----------+\n| Format |\n+-----------+\n| 1,234,568 |\n+-----------+\n \nSELECT FORMAT(123456789,2,''rm_CH'') AS ''Format'';\n \n+----------------+\n| Format |\n+----------------+\n| 123''456''789,00 |\n+----------------+ +description=Formats the given number for display as a string, adding separators to\nappropriate position and rounding the results to the given decimal position.\nFor instance, it would format 15233.345 to 15,233.35.\n\nIf the given decimal position is 0, it rounds to return no decimal point or\nfractional part. You can optionally specify a locale value to format numbers\nto the pattern appropriate for the given region.\n\nExamples\n--------\n\nSELECT FORMAT(1234567890.09876543210, 4) AS 'Format';\n+--------------------+\n| Format |\n+--------------------+\n| 1,234,567,890.0988 |\n+--------------------+\n\nSELECT FORMAT(1234567.89, 4) AS 'Format';\n+----------------+\n| Format |\n+----------------+\n| 1,234,567.8900 |\n+----------------+\n\nSELECT FORMAT(1234567.89, 0) AS 'Format';\n+-----------+\n| Format |\n+-----------+\n| 1,234,568 |\n+-----------+\n\nSELECT FORMAT(123456789,2,'rm_CH') AS 'Format';\n+----------------+\n| Format |\n+----------------+\n| 123'456'789,00 |\n+----------------+\n\nURL: https://mariadb.com/kb/en/format/ +[FORMAT_PICO_TIME] +declaration=time_val +category=Date and Time Functions +description=Given a time in picoseconds, returns a human-readable time value and unit\nindicator. Resulting unit is dependent on the length of the argument, and can\nbe:\n\n* ps - picoseconds\n* ns - nanoseconds\n* us - microseconds\n* ms - milliseconds\n* s - seconds\n* min - minutes\n* h - hours\n* d - days\n\nWith the exception of results under one nanosecond, which are not rounded and\nare represented as whole numbers, the result is rounded to 2 decimal places,\nwith a minimum of 3 significant digits.\n\nReturns NULL if the argument is NULL.\n\nThis function is very similar to the Sys Schema FORMAT_TIME function, but with\nthe following differences:\n\n* Represents minutes as min rather than m.\n* Does not represent weeks.\n\nExamples\n--------\n\nSELECT\n FORMAT_PICO_TIME(43) AS ps,\n FORMAT_PICO_TIME(4321) AS ns,\n FORMAT_PICO_TIME(43211234) AS us,\n FORMAT_PICO_TIME(432112344321) AS ms,\n FORMAT_PICO_TIME(43211234432123) AS s,\n FORMAT_PICO_TIME(432112344321234) AS m,\n FORMAT_PICO_TIME(4321123443212345) AS h,\n FORMAT_PICO_TIME(432112344321234545) AS d;\n+--------+---------+----------+-----------+---------+----------+--------+------\n-+\n| ps | ns | us | ms | s | m | h | d \n |\n+--------+---------+----------+-----------+---------+----------+--------+------\n-+\n| 43 ps | 4.32 ns | 43.21 us | 432.11 ms | 43.21 s | 7.20 min | 1.20 h | 5.00\nd |\n+--------+---------+----------+-----------+---------+----------+--------+------\n-+\n\nURL: https://mariadb.com/kb/en/format_pico_time/ [FOUND_ROWS] declaration= category=Information Functions -description=A SELECT statement may include a LIMIT clause to restrict\nthe number\nof rows the server returns to the client. In some cases, it\nis\ndesirable to know how many rows the statement would have\nreturned\nwithout the LIMIT, but without running the statement again.\nTo obtain\nthis row count, include a SQL_CALC_FOUND_ROWS option in the\nSELECT\nstatement, and then invoke FOUND_ROWS() afterwards.\n \nYou can also use FOUND_ROWS() to obtain the number of rows\nreturned by a SELECT which does not contain a LIMIT clause.\nIn this case you don''t need to use the SQL_CALC_FOUND_ROWS\noption. This can be useful for example in a stored\nprocedure.\n \nAlso, this function works with some other statements which\nreturn a resultset, including SHOW, DESC and HELP. For\nDELETE ... RETURNING you should use ROW_COUNT(). It also\nworks as a prepared statement, or after executing a prepared\nstatement.\n \nStatements which don''t return any results don''t affect\nFOUND_ROWS() - the previous value will still be returned.\n \nWarning: When used after a CALL statement, this function\nreturns the number of rows selected by the last query in the\nprocedure, not by the whole procedure.\n \nStatements using the FOUND_ROWS() function are not safe for\nreplication.\n \n\nSHOW ENGINES;\n \n+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+\n| Engine | Support | Comment | Transactions | XA |\nSavepoints |\n+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+\n| InnoDB | DEFAULT | Supports transactions, row-level\nlocking, and foreign keys | YES | YES | YES |\n...\n| SPHINX | YES | Sphinx storage engine | NO | NO | NO |\n+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+\n11 rows in set (0.01 sec)\n \nSELECT FOUND_ROWS();\n+--------------+\n| FOUND_ROWS() |\n+--------------+\n| 11 |\n+--------------+\n \nSELECT SQL_CALC_FOUND_ROWS * FROM tbl_name WHERE id > 100\nLIMIT 10;\n \nSELECT FOUND_ROWS();\n+--------------+\n| FOUND_ROWS() |\n+--------------+\n| 23 |\n+--------------+ +description=A SELECT statement may include a LIMIT clause to restrict the number of rows\nthe server returns to the client. In some cases, it is desirable to know how\nmany rows the statement would have returned without the LIMIT, but without\nrunning the statement again. To obtain this row count, include a\nSQL_CALC_FOUND_ROWS option in the SELECT statement, and then invoke\nFOUND_ROWS() afterwards.\n\nYou can also use FOUND_ROWS() to obtain the number of rows returned by a\nSELECT which does not contain a LIMIT clause. In this case you don't need to\nuse the SQL_CALC_FOUND_ROWS option. This can be useful for example in a stored\nprocedure.\n\nAlso, this function works with some other statements which return a resultset,\nincluding SHOW, DESC and HELP. For DELETE ... RETURNING you should use\nROW_COUNT(). It also works as a prepared statement, or after executing a\nprepared statement.\n\nStatements which don't return any results don't affect FOUND_ROWS() - the\nprevious value will still be returned.\n\nWarning: When used after a CALL statement, this function returns the number of\nrows selected by the last query in the procedure, not by the whole procedure.\n\nStatements using the FOUND_ROWS() function are not safe for statement-based\nreplication.\n\nExamples\n--------\n\nSHOW ENGINES\G\n*************************** 1. row ***************************\n Engine: CSV\n Support: YES\n Comment: Stores tables as CSV files\nTransactions: NO\n XA: NO\n Savepoints: NO\n*************************** 2. row ***************************\n Engine: MRG_MyISAM\n Support: YES\n Comment: Collection of identical MyISAM tables\nTransactions: NO\n XA: NO\n Savepoints: NO\n\n...\n\n*************************** 8. row ***************************\n Engine: PERFORMANCE_SCHEMA\n Support: YES\n ... [FROM_BASE64] declaration=str category=String Functions -description=Decodes the given base-64 encode string, returning the\nresult as a binary string. Returns NULL if the given string\nis NULL or if it''s invalid.\n \nIt is the reverse of the TO_BASE64 function.\n \nThere are numerous methods to base-64 encode a string.\nMariaDB uses the following:\nIt encodes alphabet value 64 as ''+''.\nIt encodes alphabet value 63 as ''/''.\nIt codes output in groups of four printable characters. Each\nthree byte of data encoded uses four characters. If the\nfinal group is incomplete, it pads the difference with the\n''='' character.\nIt divides long output, adding a new line very 76\ncharacters.\nIn decoding, it recognizes and ignores newlines, carriage\nreturns, tabs and space whitespace characters.\n \nSELECT TO_BASE64(''Maria'') AS ''Input'';\n \n+-----------+\n| Input |\n+-----------+\n| TWFyaWE= |\n+-----------+\n \nSELECT FROM_BASE64(''TWFyaWE='') AS ''Output'';\n \n+--------+\n| Output |\n+--------+\n| Maria |\n+--------+ +description=Decodes the given base-64 encode string, returning the result as a binary\nstring. Returns NULL if the given string is NULL or if it's invalid.\n\nIt is the reverse of the TO_BASE64 function.\n\nThere are numerous methods to base-64 encode a string. MariaDB uses the\nfollowing:\n\n* It encodes alphabet value 64 as '+'.\n* It encodes alphabet value 63 as '/'.\n* It codes output in groups of four printable characters. Each three byte of\ndata encoded uses four characters. If the final group is incomplete, it pads\nthe difference with the '=' character.\n* It divides long output, adding a new line very 76 characters.\n* In decoding, it recognizes and ignores newlines, carriage returns, tabs and\nspace whitespace characters.\n\nSELECT TO_BASE64('Maria') AS 'Input';\n+-----------+\n| Input |\n+-----------+\n| TWFyaWE= |\n+-----------+\n\nSELECT FROM_BASE64('TWFyaWE=') AS 'Output';\n+--------+\n| Output |\n+--------+\n| Maria |\n+--------+\n\nURL: https://mariadb.com/kb/en/from_base64/ [FROM_DAYS] declaration=N category=Date and Time Functions -description=Given a day number N, returns a DATE value. The day count is\nbased on the number of days from the start of the standard\ncalendar (0000-00-00). \n \nThe function is not designed for use with dates before the\nadvent of the Gregorian calendar in October 1582. Results\nwill not be reliable since it doesn''t account for the lost\ndays when the calendar changed from the Julian calendar.\n \nThis is the converse of the TO_DAYS() function.\n \n\nSELECT FROM_DAYS(730669);\n+-------------------+\n| FROM_DAYS(730669) |\n+-------------------+\n| 2000-07-03 |\n+-------------------+ -[FROM_UNIXTIME1] -name=FROM_UNIXTIME +description=Given a day number N, returns a DATE value. The day count is based on the\nnumber of days from the start of the standard calendar (0000-00-00).\n\nThe function is not designed for use with dates before the advent of the\nGregorian calendar in October 1582. Results will not be reliable since it\ndoesn't account for the lost days when the calendar changed from the Julian\ncalendar.\n\nThis is the converse of the TO_DAYS() function.\n\nExamples\n--------\n\nSELECT FROM_DAYS(730669);\n+-------------------+\n| FROM_DAYS(730669) |\n+-------------------+\n| 2000-07-03 |\n+-------------------+\n\nURL: https://mariadb.com/kb/en/from_days/ +[FROM_UNIXTIME] declaration=unix_timestamp category=Date and Time Functions -description=Returns a representation of the unix_timestamp argument as a\nvalue in\n''YYYY-MM-DD HH:MM:SS'' or YYYYMMDDHHMMSS.uuuuuu format,\ndepending on\nwhether the function is used in a string or numeric context.\nThe value\nis expressed in the current time zone. unix_timestamp is an\ninternal\ntimestamp value such as is produced by the UNIX_TIMESTAMP()\nfunction.\n \nIf format is given, the result is formatted according to the\nformat\nstring, which is used the same way as listed in the entry\nfor the\nDATE_FORMAT() function.\n \nTimestamps in MariaDB have a maximum value of 2147483647,\nequivalent to 2038-01-19 05:14:07. This is due to the\nunderlying 32-bit limitation. Using the function on a\ntimestamp beyond this will result in NULL being returned.\nUse DATETIME as a storage type if you require dates beyond\nthis.\n \nThe options that can be used by FROM_UNIXTIME(), as well as\nDATE_FORMAT() and STR_TO_DATE(), are:\n \nOption | Description | \n \n%a | Short weekday name in current locale (Variable\nlc_time_names). | \n \n%b | Short form month name in current locale. For locale\nen_US this is one of:\nJan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov or Dec. | \n \n%c | Month with 1 or 2 digits. | \n \n%D | Day with English suffix ''th'', ''nd'', ''st'' or\n''rd''''. (1st, 2nd, 3rd...). | \n \n%d | Day with 2 digits. | \n \n%e | Day with 1 or 2 digits. | \n \n%f | Sub seconds 6 digits. | \n \n%H | Hour with 2 digits between 00-23. | \n \n%h | Hour with 2 digits between 01-12. | \n \n%I | Hour with 2 digits between 01-12. | \n \n%i | Minute with 2 digits. | \n \n%j | Day of the year (001-366) | \n \n%k | Hour with 1 digits between 0-23. | \n \n%l | Hour with 1 digits between 1-12. | \n \n%M | Full month name in current locale (Variable\nlc_time_names). | \n \n%m | Month with 2 digits. | \n \n%p | AM/PM according to current locale (Variable\nlc_time_names). | \n \n%r | Time in 12 hour format, followed by AM/PM. Short for\n''%I:%i:%S %p''. | \n \n%S | Seconds with 2 digits. | \n \n%s | Seconds with 2 digits. | \n \n%T | Time in 24 hour format. Short for ''%H:%i:%S''. | \n \n%U | Week number (00-53), when first day of the week is\nSunday. | \n \n%u | Week number (00-53), when first day of the week is\nMonday. | \n \n%V | Week number (01-53), when first day of the week is\nSunday. Used with %X. | \n \n%v | Week number (01-53), when first day of the week is\nMonday. Used with %x. | \n \n%W | Full weekday name in current locale (Variable\nlc_time_names). | \n \n%w | Day of the week. 0 = Sunday, 1 = Saturday. | \n \n%X | Year with 4 digits when first day of the week is\nSunday. Used with %V. | \n \n%x | Year with 4 digits when first day of the week is\nSunday. Used with %v. | \n \n%Y | Year with 4 digits. | \n \n%y | Year with 2 digits. | \n \n%# | For str_to_date(), skip all numbers. | \n \n%. | For str_to_date(), skip all punctation characters. | \n \n%@ | For str_to_date(), skip all alpha characters. | \n \n%% | A literal % character. | \n \nPerformance Considerations\n \nIf your session time zone is set to SYSTEM (the default),\nFROM_UNIXTIME() will call the OS function to convert the\ndata using the system time zone. At least on Linux, the\ncorresponding function (localtime_r) uses a global mutex\ninside glibc that can cause contention under high concurrent\nload.\n \nSet your time zone to a named time zone to avoid this issue.\nSee mysql time zone tables for details on how to do this.\n \n\nSELECT FROM_UNIXTIME(1196440219);\n+---------------------------+\n| FROM_UNIXTIME(1196440219) |\n+---------------------------+\n| 2007-11-30 11:30:19 |\n+---------------------------+\n \nSELECT FROM_UNIXTIME(1196440219) + 0;\n \n+-------------------------------+\n| FROM_UNIXTIME(1196440219) + 0 |\n+-------------------------------+\n| 20071130113019.000000 |\n+-------------------------------+\n \nSELECT FROM_UNIXTIME(UNIX_TIMESTAMP(), ''%Y %D %M %h:%i:%s\n%x'');\n+---------------------------------------------------------+\n| FROM_UNIXTIME(UNIX_TIMESTAMP(), ''%Y %D %M %h:%i:%s %x'')\n|\n+---------------------------------------------------------+\n| 2010 27th March 01:03:47 2010 |\n+---------------------------------------------------------+ -[FROM_UNIXTIME2] -name=FROM_UNIXTIME -declaration=unix_timestamp,format -category=Date and Time Functions -description=Returns a representation of the unix_timestamp argument as a\nvalue in\n''YYYY-MM-DD HH:MM:SS'' or YYYYMMDDHHMMSS.uuuuuu format,\ndepending on\nwhether the function is used in a string or numeric context.\nThe value\nis expressed in the current time zone. unix_timestamp is an\ninternal\ntimestamp value such as is produced by the UNIX_TIMESTAMP()\nfunction.\n \nIf format is given, the result is formatted according to the\nformat\nstring, which is used the same way as listed in the entry\nfor the\nDATE_FORMAT() function.\n \nTimestamps in MariaDB have a maximum value of 2147483647,\nequivalent to 2038-01-19 05:14:07. This is due to the\nunderlying 32-bit limitation. Using the function on a\ntimestamp beyond this will result in NULL being returned.\nUse DATETIME as a storage type if you require dates beyond\nthis.\n \nThe options that can be used by FROM_UNIXTIME(), as well as\nDATE_FORMAT() and STR_TO_DATE(), are:\n \nOption | Description | \n \n%a | Short weekday name in current locale (Variable\nlc_time_names). | \n \n%b | Short form month name in current locale. For locale\nen_US this is one of:\nJan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov or Dec. | \n \n%c | Month with 1 or 2 digits. | \n \n%D | Day with English suffix ''th'', ''nd'', ''st'' or\n''rd''''. (1st, 2nd, 3rd...). | \n \n%d | Day with 2 digits. | \n \n%e | Day with 1 or 2 digits. | \n \n%f | Sub seconds 6 digits. | \n \n%H | Hour with 2 digits between 00-23. | \n \n%h | Hour with 2 digits between 01-12. | \n \n%I | Hour with 2 digits between 01-12. | \n \n%i | Minute with 2 digits. | \n \n%j | Day of the year (001-366) | \n \n%k | Hour with 1 digits between 0-23. | \n \n%l | Hour with 1 digits between 1-12. | \n \n%M | Full month name in current locale (Variable\nlc_time_names). | \n \n%m | Month with 2 digits. | \n \n%p | AM/PM according to current locale (Variable\nlc_time_names). | \n \n%r | Time in 12 hour format, followed by AM/PM. Short for\n''%I:%i:%S %p''. | \n \n%S | Seconds with 2 digits. | \n \n%s | Seconds with 2 digits. | \n \n%T | Time in 24 hour format. Short for ''%H:%i:%S''. | \n \n%U | Week number (00-53), when first day of the week is\nSunday. | \n \n%u | Week number (00-53), when first day of the week is\nMonday. | \n \n%V | Week number (01-53), when first day of the week is\nSunday. Used with %X. | \n \n%v | Week number (01-53), when first day of the week is\nMonday. Used with %x. | \n \n%W | Full weekday name in current locale (Variable\nlc_time_names). | \n \n%w | Day of the week. 0 = Sunday, 1 = Saturday. | \n \n%X | Year with 4 digits when first day of the week is\nSunday. Used with %V. | \n \n%x | Year with 4 digits when first day of the week is\nSunday. Used with %v. | \n \n%Y | Year with 4 digits. | \n \n%y | Year with 2 digits. | \n \n%# | For str_to_date(), skip all numbers. | \n \n%. | For str_to_date(), skip all punctation characters. | \n \n%@ | For str_to_date(), skip all alpha characters. | \n \n%% | A literal % character. | \n \nPerformance Considerations\n \nIf your session time zone is set to SYSTEM (the default),\nFROM_UNIXTIME() will call the OS function to convert the\ndata using the system time zone. At least on Linux, the\ncorresponding function (localtime_r) uses a global mutex\ninside glibc that can cause contention under high concurrent\nload.\n \nSet your time zone to a named time zone to avoid this issue.\nSee mysql time zone tables for details on how to do this.\n \n\nSELECT FROM_UNIXTIME(1196440219);\n+---------------------------+\n| FROM_UNIXTIME(1196440219) |\n+---------------------------+\n| 2007-11-30 11:30:19 |\n+---------------------------+\n \nSELECT FROM_UNIXTIME(1196440219) + 0;\n \n+-------------------------------+\n| FROM_UNIXTIME(1196440219) + 0 |\n+-------------------------------+\n| 20071130113019.000000 |\n+-------------------------------+\n \nSELECT FROM_UNIXTIME(UNIX_TIMESTAMP(), ''%Y %D %M %h:%i:%s\n%x'');\n+---------------------------------------------------------+\n| FROM_UNIXTIME(UNIX_TIMESTAMP(), ''%Y %D %M %h:%i:%s %x'')\n|\n+---------------------------------------------------------+\n| 2010 27th March 01:03:47 2010 |\n+---------------------------------------------------------+ +description=Returns a representation of the unix_timestamp argument as a value in\n'YYYY-MM-DD HH:MM:SS' or YYYYMMDDHHMMSS.uuuuuu format, depending on whether\nthe function is used in a string or numeric context. The value is expressed in\nthe current time zone. unix_timestamp is an internal timestamp value such as\nis produced by the UNIX_TIMESTAMP() function.\n\nIf format is given, the result is formatted according to the format string,\nwhich is used the same way as listed in the entry for the DATE_FORMAT()\nfunction.\n\nTimestamps in MariaDB have a maximum value of 2147483647, equivalent to\n2038-01-19 05:14:07. This is due to the underlying 32-bit limitation. Using\nthe function on a timestamp beyond this will result in NULL being returned.\nUse DATETIME as a storage type if you require dates beyond this.\n\nThe options that can be used by FROM_UNIXTIME(), as well as DATE_FORMAT() and\nSTR_TO_DATE(), are:\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| %a | Short weekday name in current locale |\n| | (Variable lc_time_names). |\n+---------------------------+------------------------------------------------+\n| %b | Short form month name in current locale. For |\n| | locale en_US this is one of: |\n| | Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov |\n| | or Dec. |\n+---------------------------+------------------------------------------------+\n| %c | Month with 1 or 2 digits. |\n+---------------------------+------------------------------------------------+\n| %D | Day with English suffix 'th', 'nd', 'st' or |\n| | 'rd''. (1st, 2nd, 3rd...). |\n+---------------------------+------------------------------------------------+\n| %d | Day with 2 digits. |\n+---------------------------+------------------------------------------------+\n| %e | Day with 1 or 2 digits. |\n+---------------------------+------------------------------------------------+\n| %f | Microseconds 6 digits. |\n+---------------------------+------------------------------------------------+\n| %H | Hour with 2 digits between 00-23. |\n+---------------------------+------------------------------------------------+\n| %h | Hour with 2 digits between 01-12. |\n+---------------------------+------------------------------------------------+\n| %I | Hour with 2 digits between 01-12. |\n+---------------------------+------------------------------------------------+\n| %i | Minute with 2 digits. |\n+---------------------------+------------------------------------------------+\n| %j | Day of the year (001-366) |\n+---------------------------+------------------------------------------------+\n ... [GEOMETRYCOLLECTION] declaration=g1,g2,... category=Geometry Constructors -description=Constructs a WKB GeometryCollection. If any argument is not\na well-formed WKB representation of a geometry, the return\nvalue is NULL.\n \n\nCREATE TABLE gis_geometrycollection (g GEOMETRYCOLLECTION);\nSHOW FIELDS FROM gis_geometrycollection;\n \nINSERT INTO gis_geometrycollection VALUES\n (GeomCollFromText(''GEOMETRYCOLLECTION(POINT(0 0),\nLINESTRING(0 0,10 10))'')),\n (GeometryFromWKB(AsWKB(GeometryCollection(Point(44, 6),\nLineString(Point(3, 6), Point(7, 9)))))),\n (GeomFromText(''GeometryCollection()'')),\n (GeomFromText(''GeometryCollection EMPTY'')); +description=Constructs a WKB GeometryCollection. If any argument is not a well-formed WKB\nrepresentation of a geometry, the return value is NULL.\n\nExamples\n--------\n\nCREATE TABLE gis_geometrycollection (g GEOMETRYCOLLECTION);\nSHOW FIELDS FROM gis_geometrycollection;\nINSERT INTO gis_geometrycollection VALUES\n (GeomCollFromText('GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0,10\n10))')),\n (GeometryFromWKB(AsWKB(GeometryCollection(Point(44, 6),\nLineString(Point(3, 6), Point(7, 9)))))),\n (GeomFromText('GeometryCollection()')),\n (GeomFromText('GeometryCollection EMPTY'));\n\nURL: https://mariadb.com/kb/en/geometrycollection/ [GET_FORMAT] -declaration={DATE|DATETIME|TIME}, {''EUR''|''USA''|''JIS''|''ISO''|''INTERNAL''} +declaration={DATE|DATETIME|TIME}, {'EUR'|'USA'|'JIS'|'ISO'|'INTERNAL'} category=Date and Time Functions -description=Returns a format string. This function is useful in\ncombination with\nthe DATE_FORMAT() and the STR_TO_DATE() functions.\n \nPossible result formats are:\n \nFunction Call | Result Format | \n \nGET_FORMAT(DATE,''EUR'') | ''%d.%m.%Y'' | \n \nGET_FORMAT(DATE,''USA'') | ''%m.%d.%Y'' | \n \nGET_FORMAT(DATE,''JIS'') | ''%Y-%m-%d'' | \n \nGET_FORMAT(DATE,''ISO'') | ''%Y-%m-%d'' | \n \nGET_FORMAT(DATE,''INTERNAL'') | ''%Y%m%d'' | \n \nGET_FORMAT(DATETIME,''EUR'') | ''%Y-%m-%d %H.%i.%s'' | \n \nGET_FORMAT(DATETIME,''USA'') | ''%Y-%m-%d %H.%i.%s'' | \n \nGET_FORMAT(DATETIME,''JIS'') | ''%Y-%m-%d %H:%i:%s'' | \n \nGET_FORMAT(DATETIME,''ISO'') | ''%Y-%m-%d %H:%i:%s'' | \n \nGET_FORMAT(DATETIME,''INTERNAL'') | ''%Y%m%d%H%i%s'' | \n \nGET_FORMAT(TIME,''EUR'') | ''%H.%i.%s'' | \n \nGET_FORMAT(TIME,''USA'') | ''%h:%i:%s %p'' | \n \nGET_FORMAT(TIME,''JIS'') | ''%H:%i:%s'' | \n \nGET_FORMAT(TIME,''ISO'') | ''%H:%i:%s'' | \n \nGET_FORMAT(TIME,''INTERNAL'') | ''%H%i%s'' | \n \n\nObtaining the string matching to the standard European date\nformat:\n \nSELECT GET_FORMAT(DATE, ''EUR'');\n+-------------------------+\n| GET_FORMAT(DATE, ''EUR'') |\n+-------------------------+\n| %d.%m.%Y |\n+-------------------------+\n \nUsing the same string to format a date:\n \nSELECT DATE_FORMAT(''2003-10-03'',GET_FORMAT(DATE,''EUR''));\n+--------------------------------------------------+\n| DATE_FORMAT(''2003-10-03'',GET_FORMAT(DATE,''EUR'')) |\n+--------------------------------------------------+\n| 03.10.2003 |\n+--------------------------------------------------+\n \nSELECT STR_TO_DATE(''10.31.2003'',GET_FORMAT(DATE,''USA''));\n+--------------------------------------------------+\n| STR_TO_DATE(''10.31.2003'',GET_FORMAT(DATE,''USA'')) |\n+--------------------------------------------------+\n| 2003-10-31 |\n+--------------------------------------------------+ +description=Returns a format string. This function is useful in combination with the\nDATE_FORMAT() and the STR_TO_DATE() functions.\n\nPossible result formats are:\n\n+--------------------------------------+--------------------------------------+\n| Function Call | Result Format |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(DATE,'EUR') | '%d.%m.%Y' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(DATE,'USA') | '%m.%d.%Y' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(DATE,'JIS') | '%Y-%m-%d' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(DATE,'ISO') | '%Y-%m-%d' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(DATE,'INTERNAL') | '%Y%m%d' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(DATETIME,'EUR') | '%Y-%m-%d %H.%i.%s' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(DATETIME,'USA') | '%Y-%m-%d %H.%i.%s' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(DATETIME,'JIS') | '%Y-%m-%d %H:%i:%s' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(DATETIME,'ISO') | '%Y-%m-%d %H:%i:%s' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(DATETIME,'INTERNAL') | '%Y%m%d%H%i%s' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(TIME,'EUR') | '%H.%i.%s' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(TIME,'USA') | '%h:%i:%s %p' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(TIME,'JIS') | '%H:%i:%s' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(TIME,'ISO') | '%H:%i:%s' |\n+--------------------------------------+--------------------------------------+\n| GET_FORMAT(TIME,'INTERNAL') | '%H%i%s' |\n+--------------------------------------+--------------------------------------+\n\nExamples\n--------\n\nObtaining the string matching to the standard European date format:\n\nSELECT GET_FORMAT(DATE, 'EUR');\n+-------------------------+\n| GET_FORMAT(DATE, 'EUR') |\n+-------------------------+\n| %d.%m.%Y |\n+-------------------------+\n ... [GET_LOCK] declaration=str,timeout category=Miscellaneous Functions -description=Tries to obtain a lock with a name given by the string str,\nusing a timeout of timeout seconds. Returns 1 if the lock\nwas obtained successfully, 0 if the attempt timed out (for\nexample, because another client has previously locked the\nname), or NULL if an error occurred (such as running out of\nmemory or the thread was killed with mysqladmin kill).\n \nA lock is released with RELEASE_LOCK(), when the connection\nterminates (either normally or abnormally), or before\nMariaDB 10.0.2, when the connection executes another\nGET_LOCK statement. From MariaDB 10.0.2, a connection can\nhold multiple locks at the same time, so a lock that is no\nlonger needed needs to be explicitly released.\n \nThe IS_FREE_LOCK function returns whether a specified lock a\nfree or not, and the IS_USED_LOCK whether the function is in\nuse or not.\n \nLocks obtained with GET_LOCK() do not interact with\ntransactions. That is, committing a transaction does not\nrelease any such locks obtained during the transaction.\n \nFrom MariaDB 10.0.2, it is also possible to recursively set\nthe same lock. If a lock with the same name is set n times,\nit needs to be released n times as well. \n \nstr is case insensitive for GET_LOCK() and related\nfunctions. If str is an empty string or NULL, GET_LOCK()\nreturns NULL and does nothing. From MariaDB 10.2.2, timeout\nsupports microseconds. Before then, it was rounded to the\nclosest integer.\n \nIf the metadata_lock_info plugin is installed, locks\nacquired with this function are visible in the Information\nSchema METADATA_LOCK_INFO table.\n \nThis function can be used to implement application locks or\nto simulate record locks. Names are locked on a server-wide\nbasis. If a name has been locked by one client, GET_LOCK()\nblocks any request by another client for a lock with the\nsame name. This allows clients that agree on a given lock\nname to use the name to perform cooperative advisory\nlocking. But be aware that it also allows a client that is\nnot among the set of cooperating clients to lock a name,\neither inadvertently or deliberately, and thus prevent any\nof the cooperating clients from locking that name. One way\nto reduce the likelihood of this is to use lock names that\nare database-specific or application-specific. For example,\nuse lock names of the form db_name.str or app_name.str.\n \nStatements using the GET_LOCK() function are not safe for\nreplication.\n \nThe patch to permit multiple locks was contributed by\nKonstantin "Kostja" Osipov (MDEV-3917).\n \n\nSELECT GET_LOCK(''lock1'',10);\n+----------------------+\n| GET_LOCK(''lock1'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nSELECT IS_FREE_LOCK(''lock1''), IS_USED_LOCK(''lock1'');\n+-----------------------+-----------------------+\n| IS_FREE_LOCK(''lock1'') | IS_USED_LOCK(''lock1'') |\n+-----------------------+-----------------------+\n| 0 | 46 |\n+-----------------------+-----------------------+\n \nSELECT IS_FREE_LOCK(''lock2''), IS_USED_LOCK(''lock2'');\n+-----------------------+-----------------------+\n| IS_FREE_LOCK(''lock2'') | IS_USED_LOCK(''lock2'') |\n+-----------------------+-----------------------+\n| 1 | NULL |\n+-----------------------+-----------------------+\n \nFrom MariaDB 10.0.2, multiple locks can be held:\n \nSELECT GET_LOCK(''lock2'',10);\n+----------------------+\n| GET_LOCK(''lock2'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nSELECT IS_FREE_LOCK(''lock1''), IS_FREE_LOCK(''lock2'');\n+-----------------------+-----------------------+\n| IS_FREE_LOCK(''lock1'') | IS_FREE_LOCK(''lock2'') |\n+-----------------------+-----------------------+\n| 0 | 0 |\n+-----------------------+-----------------------+\n \nSELECT RELEASE_LOCK(''lock1''), RELEASE_LOCK(''lock2'');\n+-----------------------+-----------------------+\n| RELEASE_LOCK(''lock1'') | RELEASE_LOCK(''lock2'') |\n+-----------------------+-----------------------+\n| 1 | 1 |\n+-----------------------+-----------------------+\n \nBefore MariaDB 10.0.2, a connection could only hold a single\nlock:\n \nSELECT GET_LOCK(''lock2'',10);\n+----------------------+\n| GET_LOCK(''lock2'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nSELECT IS_FREE_LOCK(''lock1''), IS_FREE_LOCK(''lock2'');\n+-----------------------+-----------------------+\n| IS_FREE_LOCK(''lock1'') | IS_FREE_LOCK(''lock2'') |\n+-----------------------+-----------------------+\n| 1 | 0 |\n+-----------------------+-----------------------+\n \nSELECT RELEASE_LOCK(''lock1''), RELEASE_LOCK(''lock2'');\n+-----------------------+-----------------------+\n| RELEASE_LOCK(''lock1'') | RELEASE_LOCK(''lock2'') |\n+-----------------------+-----------------------+\n| NULL | 1 |\n+-----------------------+-----------------------+\n \nFrom MariaDB 10.0.2, it is possible to hold the same lock\nrecursively. This example is viewed using the\nmetadata_lock_info plugin:\n \nSELECT GET_LOCK(''lock3'',10);\n+----------------------+\n| GET_LOCK(''lock3'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nSELECT GET_LOCK(''lock3'',10);\n+----------------------+\n| GET_LOCK(''lock3'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\n \n+-----------+---------------------+---------------+-----------+--------------+------------+\n| THREAD_ID | LOCK_MODE | LOCK_DURATION | LOCK_TYPE |\nTABLE_SCHEMA | TABLE_NAME |\n+-----------+---------------------+---------------+-----------+--------------+------------+\n| 46 | MDL_SHARED_NO_WRITE | NULL | User lock | lock3 | |\n+-----------+---------------------+---------------+-----------+--------------+------------+\n \nSELECT RELEASE_LOCK(''lock3'');\n+-----------------------+\n| RELEASE_LOCK(''lock3'') |\n+-----------------------+\n| 1 |\n+-----------------------+\n \nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\n \n+-----------+---------------------+---------------+-----------+--------------+------------+\n| THREAD_ID | LOCK_MODE | LOCK_DURATION | LOCK_TYPE |\nTABLE_SCHEMA | TABLE_NAME |\n+-----------+---------------------+---------------+-----------+--------------+------------+\n| 46 | MDL_SHARED_NO_WRITE | NULL | User lock | lock3 | |\n+-----------+---------------------+---------------+-----------+--------------+------------+\n \nSELECT RELEASE_LOCK(''lock3'');\n+-----------------------+\n| RELEASE_LOCK(''lock3'') |\n+-----------------------+\n| 1 |\n+-----------------------+\n \nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\n \nEmpty set (0.000 sec)\n \nTimeout example: Connection 1:\n \nSELECT GET_LOCK(''lock4'',10);\n+----------------------+\n| GET_LOCK(''lock4'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nConnection 2:\n \nSELECT GET_LOCK(''lock4'',10);\n \nAfter 10 seconds...\n \n+----------------------+\n| GET_LOCK(''lock4'',10) |\n+----------------------+\n| 0 |\n+----------------------+\n \nDeadlocks are automatically detected and resolved.\nConnection 1:\n \nSELECT GET_LOCK(''lock5'',10); \n+----------------------+\n| GET_LOCK(''lock5'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nConnection 2:\n \nSELECT GET_LOCK(''lock6'',10);\n+----------------------+\n| GET_LOCK(''lock6'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nConnection 1:\n \nSELECT GET_LOCK(''lock6'',10); \n+----------------------+\n| GET_LOCK(''lock6'',10) |\n+----------------------+\n| 0 |\n+----------------------+\n \nConnection 2:\n \nSELECT GET_LOCK(''lock5'',10);\nERROR 1213 (40001): Deadlock found when trying to get lock;\n try restarting transaction +description=Tries to obtain a lock with a name given by the string str, using a timeout of\ntimeout seconds. Returns 1 if the lock was obtained successfully, 0 if the\nattempt timed out (for example, because another client has previously locked\nthe name), or NULL if an error occurred (such as running out of memory or the\nthread was killed with mariadb-admin kill).\n\nA lock is released with RELEASE_LOCK(), when the connection terminates (either\nnormally or abnormally). A connection can hold multiple locks at the same\ntime, so a lock that is no longer needed needs to be explicitly released.\n\nThe IS_FREE_LOCK function returns whether a specified lock a free or not, and\nthe IS_USED_LOCK whether the function is in use or not.\n\nLocks obtained with GET_LOCK() do not interact with transactions. That is,\ncommitting a transaction does not release any such locks obtained during the\ntransaction.\n\nIt is also possible to recursively set the same lock. If a lock with the same\nname is set n times, it needs to be released n times as well.\n\nstr is case insensitive for GET_LOCK() and related functions. If str is an\nempty string or NULL, GET_LOCK() returns NULL and does nothing. timeout\nsupports microseconds.\n\nIf the metadata_lock_info plugin is installed, locks acquired with this\nfunction are visible in the Information Schema METADATA_LOCK_INFO table.\n\nThis function can be used to implement application locks or to simulate record\nlocks. Names are locked on a server-wide basis. If a name has been locked by\none client, GET_LOCK() blocks any request by another client for a lock with\nthe same name. This allows clients that agree on a given lock name to use the\nname to perform cooperative advisory locking. But be aware that it also allows\na client that is not among the set of cooperating clients to lock a name,\neither inadvertently or deliberately, and thus prevent any of the cooperating\nclients from locking that name. One way to reduce the likelihood of this is to\nuse lock names that are database-specific or application-specific. For\nexample, use lock names of the form db_name.str or app_name.str.\n\nStatements using the GET_LOCK function are not safe for statement-based\nreplication.\n\nThe patch to permit multiple locks was contributed by Konstantin "Kostja"\nOsipov (MDEV-3917).\n\nExamples\n--------\n\nSELECT GET_LOCK('lock1',10);\n+----------------------+\n| GET_LOCK('lock1',10) |\n ... [GLENGTH] declaration=ls category=LineString Properties -description=Returns as a double-precision number the length of the\nLineString value ls in its associated spatial reference.\n \n\nSET @ls = ''LineString(1 1,2 2,3 3)'';\n \nSELECT GLength(GeomFromText(@ls));\n+----------------------------+\n| GLength(GeomFromText(@ls)) |\n+----------------------------+\n| 2.82842712474619 |\n+----------------------------+ +description=Returns as a double-precision number the length of the LineString value ls in\nits associated spatial reference.\n\nExamples\n--------\n\nSET @ls = 'LineString(1 1,2 2,3 3)';\n\nSELECT GLength(GeomFromText(@ls));\n+----------------------------+\n| GLength(GeomFromText(@ls)) |\n+----------------------------+\n| 2.82842712474619 |\n+----------------------------+\n\nURL: https://mariadb.com/kb/en/glength/ [GREATEST] declaration=value1,value2,... category=Comparison Operators -description=With two or more arguments, returns the largest\n(maximum-valued)\nargument. The arguments are compared using the same rules as\nfor\nLEAST().\n \n\nSELECT GREATEST(2,0);\n+---------------+\n| GREATEST(2,0) |\n+---------------+\n| 2 |\n+---------------+\n \nSELECT GREATEST(34.0,3.0,5.0,767.0);\n+------------------------------+\n| GREATEST(34.0,3.0,5.0,767.0) |\n+------------------------------+\n| 767.0 |\n+------------------------------+\n \nSELECT GREATEST(''B'',''A'',''C'');\n+-----------------------+\n| GREATEST(''B'',''A'',''C'') |\n+-----------------------+\n| C |\n+-----------------------+ +description=With two or more arguments, returns the largest (maximum-valued) argument. The\narguments are compared using the same rules as for LEAST().\n\nExamples\n--------\n\nSELECT GREATEST(2,0);\n+---------------+\n| GREATEST(2,0) |\n+---------------+\n| 2 |\n+---------------+\n\nSELECT GREATEST(34.0,3.0,5.0,767.0);\n+------------------------------+\n| GREATEST(34.0,3.0,5.0,767.0) |\n+------------------------------+\n| 767.0 |\n+------------------------------+\n\nSELECT GREATEST('B','A','C');\n+-----------------------+\n| GREATEST('B','A','C') |\n+-----------------------+\n| C |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/greatest/ [GROUP_CONCAT] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=This function returns a string result with the concatenated\nnon-NULL\nvalues from a group. It returns NULL if there are no\nnon-NULL values.\n \nThe maximum returned length in bytes is determined by the\ngroup_concat_max_len server system variable, which defaults\nto 1M (>= MariaDB 10.2.4) or 1K ( +description=This function returns a string result with the concatenated non-NULL values\nfrom a group. If any expr in GROUP_CONCAT evaluates to NULL, that tuple is not\npresent in the list returned by GROUP_CONCAT.\n\nIt returns NULL if all arguments are NULL, or there are no matching rows.\n\nThe maximum returned length in bytes is determined by the group_concat_max_len\nserver system variable, which defaults to 1M.\n\nIf group_concat_max_len <= 512, the return type is VARBINARY or VARCHAR;\notherwise, the return type is BLOB or TEXT. The choice between binary or\nnon-binary types depends from the input.\n\nThe full syntax is as follows:\n\nGROUP_CONCAT([DISTINCT] expr [,expr ...]\n [ORDER BY {unsigned_integer | col_name | expr}\n [ASC | DESC] [,col_name ...]]\n [SEPARATOR str_val]\n [LIMIT {[offset,] row_count | row_count OFFSET offset}])\n\nDISTINCT eliminates duplicate values from the output string.\n\nORDER BY determines the order of returned values.\n\nSEPARATOR specifies a separator between the values. The default separator is a\ncomma (,). It is possible to avoid using a separator by specifying an empty\nstring.\n\nLIMIT\n-----\n\nThe LIMIT clause can be used with GROUP_CONCAT. This was not possible prior to\nMariaDB 10.3.3.\n\nExamples\n--------\n\nSELECT student_name,\n GROUP_CONCAT(test_score)\n FROM student\n GROUP BY student_name;\n\nGet a readable list of MariaDB users from the mysql.user table:\n\nSELECT GROUP_CONCAT(DISTINCT User ORDER BY User SEPARATOR '\n')\n FROM mysql.user;\n\nIn the former example, DISTINCT is used because the same user may occur more\nthan once. The new line (\n) used as a SEPARATOR makes the results easier to\n ... [HEX] declaration=N_or_S category=String Functions -description=If N_or_S is a number, returns a string representation of\nthe hexadecimal\nvalue of N, where N is a longlong (BIGINT) number. This is\nequivalent to CONV(N,10,16).\n \nIf N_or_S is a string, returns a hexadecimal string\nrepresentation of\nN_or_S where each byte of each character in N_or_S is\nconverted to two hexadecimal\ndigits. If N_or_S is NULL, returns NULL. The inverse of this\noperation is performed by the UNHEX()\nfunction.\n \n\nSELECT HEX(255);\n+----------+\n| HEX(255) |\n+----------+\n| FF |\n+----------+\n \nSELECT 0x4D617269614442;\n \n+------------------+\n| 0x4D617269614442 |\n+------------------+\n| MariaDB |\n+------------------+\n \nSELECT HEX(''MariaDB'');\n+----------------+\n| HEX(''MariaDB'') |\n+----------------+\n| 4D617269614442 |\n+----------------+ +description=If N_or_S is a number, returns a string representation of the hexadecimal\nvalue of N, where N is a longlong (BIGINT) number. This is equivalent to\nCONV(N,10,16).\n\nIf N_or_S is a string, returns a hexadecimal string representation of N_or_S\nwhere each byte of each character in N_or_S is converted to two hexadecimal\ndigits. If N_or_S is NULL, returns NULL. The inverse of this operation is\nperformed by the UNHEX() function.\n\nMariaDB starting with 10.5.0\n----------------------------\nHEX() with an INET6 argument returns a hexadecimal representation of the\nunderlying 16-byte binary string.\n\nExamples\n--------\n\nSELECT HEX(255);\n+----------+\n| HEX(255) |\n+----------+\n| FF |\n+----------+\n\nSELECT 0x4D617269614442;\n+------------------+\n| 0x4D617269614442 |\n+------------------+\n| MariaDB |\n+------------------+\n\nSELECT HEX('MariaDB');\n+----------------+\n| HEX('MariaDB') |\n+----------------+\n| 4D617269614442 |\n+----------------+\n\nFrom MariaDB 10.5.0:\n\nSELECT HEX(CAST('2001:db8::ff00:42:8329' AS INET6));\n+----------------------------------------------+\n| HEX(CAST('2001:db8::ff00:42:8329' AS INET6)) |\n+----------------------------------------------+\n| 20010DB8000000000000FF0000428329 |\n+----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/hex/ [HOUR] declaration=time category=Date and Time Functions -description=Returns the hour for time. The range of the return value is\n0 to 23\nfor time-of-day values. However, the range of TIME values\nactually is\nmuch larger, so HOUR can return values greater than 23.\n \nThe return value is always positive, even if a negative TIME\nvalue is provided.\n \n\nSELECT HOUR(''10:05:03'');\n+------------------+\n| HOUR(''10:05:03'') |\n+------------------+\n| 10 |\n+------------------+\n \nSELECT HOUR(''272:59:59'');\n+-------------------+\n| HOUR(''272:59:59'') |\n+-------------------+\n| 272 |\n+-------------------+\n \nDifference between EXTRACT (HOUR FROM ...) (>= MariaDB\n10.0.7 and MariaDB 5.5.35) and HOUR:\n \nSELECT EXTRACT(HOUR FROM ''26:30:00''), HOUR(''26:30:00'');\n+-------------------------------+------------------+\n| EXTRACT(HOUR FROM ''26:30:00'') | HOUR(''26:30:00'') |\n+-------------------------------+------------------+\n| 2 | 26 |\n+-------------------------------+------------------+ +description=Returns the hour for time. The range of the return value is 0 to 23 for\ntime-of-day values. However, the range of TIME values actually is much larger,\nso HOUR can return values greater than 23.\n\nThe return value is always positive, even if a negative TIME value is provided.\n\nExamples\n--------\n\nSELECT HOUR('10:05:03');\n+------------------+\n| HOUR('10:05:03') |\n+------------------+\n| 10 |\n+------------------+\n\nSELECT HOUR('272:59:59');\n+-------------------+\n| HOUR('272:59:59') |\n+-------------------+\n| 272 |\n+-------------------+\n\nDifference between EXTRACT (HOUR FROM ...) (>= MariaDB 10.0.7 and MariaDB\n5.5.35) and HOUR:\n\nSELECT EXTRACT(HOUR FROM '26:30:00'), HOUR('26:30:00');\n+-------------------------------+------------------+\n| EXTRACT(HOUR FROM '26:30:00') | HOUR('26:30:00') |\n+-------------------------------+------------------+\n| 2 | 26 |\n+-------------------------------+------------------+\n\nURL: https://mariadb.com/kb/en/hour/ [IFNULL] declaration=expr1,expr2 category=Control Flow Functions -description=If expr1 is not NULL, IFNULL() returns expr1; otherwise it\nreturns\nexpr2. IFNULL() returns a numeric or string value, depending\non the\ncontext in which it is used.\n \n\nSELECT IFNULL(1,0); \n+-------------+\n| IFNULL(1,0) |\n+-------------+\n| 1 |\n+-------------+\n \nSELECT IFNULL(NULL,10);\n+-----------------+\n| IFNULL(NULL,10) |\n+-----------------+\n| 10 |\n+-----------------+\n \nSELECT IFNULL(1/0,10);\n+----------------+\n| IFNULL(1/0,10) |\n+----------------+\n| 10.0000 |\n+----------------+\n \nSELECT IFNULL(1/0,''yes'');\n+-------------------+\n| IFNULL(1/0,''yes'') |\n+-------------------+\n| yes |\n+-------------------+ +description=If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns expr2.\nIFNULL() returns a numeric or string value, depending on the context in which\nit is used.\n\nFrom MariaDB 10.3, NVL() is an alias for IFNULL().\n\nExamples\n--------\n\nSELECT IFNULL(1,0); \n+-------------+\n| IFNULL(1,0) |\n+-------------+\n| 1 |\n+-------------+\n\nSELECT IFNULL(NULL,10);\n+-----------------+\n| IFNULL(NULL,10) |\n+-----------------+\n| 10 |\n+-----------------+\n\nSELECT IFNULL(1/0,10);\n+----------------+\n| IFNULL(1/0,10) |\n+----------------+\n| 10.0000 |\n+----------------+\n\nSELECT IFNULL(1/0,'yes');\n+-------------------+\n| IFNULL(1/0,'yes') |\n+-------------------+\n| yes |\n+-------------------+\n\nURL: https://mariadb.com/kb/en/ifnull/ +[IN] +declaration=value,... +category=Comparison Operators +description=Returns 1 if expr is equal to any of the values in the IN list, else returns\n0. If all values are constants, they are evaluated according to the type of\nexpr and sorted. The search for the item then is done using a binary search.\nThis means IN is very quick if the IN value list consists entirely of\nconstants. Otherwise, type conversion takes place according to the rules\ndescribed at Type Conversion, but applied to all the arguments.\n\nIf expr is NULL, IN always returns NULL. If at least one of the values in the\nlist is NULL, and one of the comparisons is true, the result is 1. If at least\none of the values in the list is NULL and none of the comparisons is true, the\nresult is NULL.\n\nExamples\n--------\n\nSELECT 2 IN (0,3,5,7);\n+----------------+\n| 2 IN (0,3,5,7) |\n+----------------+\n| 0 |\n+----------------+\n\nSELECT 'wefwf' IN ('wee','wefwf','weg');\n+----------------------------------+\n| 'wefwf' IN ('wee','wefwf','weg') |\n+----------------------------------+\n| 1 |\n+----------------------------------+\n\nType conversion:\n\nSELECT 1 IN ('1', '2', '3');\n+----------------------+\n| 1 IN ('1', '2', '3') |\n+----------------------+\n| 1 |\n+----------------------+\n\nSELECT NULL IN (1, 2, 3);\n+-------------------+\n| NULL IN (1, 2, 3) |\n+-------------------+\n| NULL |\n+-------------------+\n\nSELECT 1 IN (1, 2, NULL);\n+-------------------+\n| 1 IN (1, 2, NULL) |\n+-------------------+\n| 1 |\n ... [INET6_ATON] declaration=expr category=Miscellaneous Functions -description=Given an IPv6 or IPv4 network address as a string, returns a\nbinary string that represents the numeric value of the\naddress.\n \nNo trailing zone ID''s or traling network masks are\npermitted. For IPv4 addresses, or IPv6 addresses with IPv4\naddress parts, no classful addresses or trailing port\nnumbers are permitted and octal numbers are not supported.\n \nThe returned binary string will be VARBINARY(16) or\nVARBINARY(4) for IPv6 and IPv4 addresses respectively.\n \nReturns NULL if the argument is not understood.\n \n\nSELECT HEX(INET6_ATON(''10.0.1.1''));\n+-----------------------------+\n| HEX(INET6_ATON(''10.0.1.1'')) |\n+-----------------------------+\n| 0A000101 |\n+-----------------------------+\n \nSELECT HEX(INET6_ATON(''48f3::d432:1431:ba23:846f''));\n+----------------------------------------------+\n| HEX(INET6_ATON(''48f3::d432:1431:ba23:846f'')) |\n+----------------------------------------------+\n| 48F3000000000000D4321431BA23846F |\n+----------------------------------------------+ +description=Given an IPv6 or IPv4 network address as a string, returns a binary string\nthat represents the numeric value of the address.\n\nNo trailing zone ID's or traling network masks are permitted. For IPv4\naddresses, or IPv6 addresses with IPv4 address parts, no classful addresses or\ntrailing port numbers are permitted and octal numbers are not supported.\n\nThe returned binary string will be VARBINARY(16) or VARBINARY(4) for IPv6 and\nIPv4 addresses respectively.\n\nReturns NULL if the argument is not understood.\n\nMariaDB starting with 10.5.0\n----------------------------\nFrom MariaDB 10.5.0, INET6_ATON can take INET6 as an argument.\n\nExamples\n--------\n\nSELECT HEX(INET6_ATON('10.0.1.1'));\n+-----------------------------+\n| HEX(INET6_ATON('10.0.1.1')) |\n+-----------------------------+\n| 0A000101 |\n+-----------------------------+\n\nSELECT HEX(INET6_ATON('48f3::d432:1431:ba23:846f'));\n+----------------------------------------------+\n| HEX(INET6_ATON('48f3::d432:1431:ba23:846f')) |\n+----------------------------------------------+\n| 48F3000000000000D4321431BA23846F |\n+----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/inet6_aton/ [INET6_NTOA] declaration=expr category=Miscellaneous Functions -description=Given an IPv6 or IPv4 network address as a numeric binary\nstring, returns the address as a nonbinary string in the\nconnection character set.\n \nThe return string is lowercase, and is platform independent,\nsince it does not use functions specific to the operating\nsystem. It has a maximum length of 39 characters.\n \nReturns NULL if the argument is not understood.\n \n\nSELECT INET6_NTOA(UNHEX(''0A000101''));\n+-------------------------------+\n| INET6_NTOA(UNHEX(''0A000101'')) |\n+-------------------------------+\n| 10.0.1.1 |\n+-------------------------------+\n \nSELECT\nINET6_NTOA(UNHEX(''48F3000000000000D4321431BA23846F''));\n+-------------------------------------------------------+\n| INET6_NTOA(UNHEX(''48F3000000000000D4321431BA23846F'')) |\n+-------------------------------------------------------+\n| 48f3::d432:1431:ba23:846f |\n+-------------------------------------------------------+ +description=Given an IPv6 or IPv4 network address as a numeric binary string, returns the\naddress as a nonbinary string in the connection character set.\n\nThe return string is lowercase, and is platform independent, since it does not\nuse functions specific to the operating system. It has a maximum length of 39\ncharacters.\n\nReturns NULL if the argument is not understood.\n\nExamples\n--------\n\nSELECT INET6_NTOA(UNHEX('0A000101'));\n+-------------------------------+\n| INET6_NTOA(UNHEX('0A000101')) |\n+-------------------------------+\n| 10.0.1.1 |\n+-------------------------------+\n\nSELECT INET6_NTOA(UNHEX('48F3000000000000D4321431BA23846F'));\n+-------------------------------------------------------+\n| INET6_NTOA(UNHEX('48F3000000000000D4321431BA23846F')) |\n+-------------------------------------------------------+\n| 48f3::d432:1431:ba23:846f |\n+-------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/inet6_ntoa/ [INET_ATON] declaration=expr category=Miscellaneous Functions -description=Given the dotted-quad representation of an IPv4 network\naddress as a string,\nreturns an integer that represents the numeric value of the\naddress.\nAddresses may be 4- or 8-byte addresses.\n \nReturns NULL if the argument is not understood.\n \n\nSELECT INET_ATON(''192.168.1.1'');\n+--------------------------+\n| INET_ATON(''192.168.1.1'') |\n+--------------------------+\n| 3232235777 |\n+--------------------------+\n \nThis is calculated as follows: 192 x 2563 + 168 x 256 2 + 1\nx 256 + 1 +description=Given the dotted-quad representation of an IPv4 network address as a string,\nreturns an integer that represents the numeric value of the address. Addresses\nmay be 4- or 8-byte addresses.\n\nReturns NULL if the argument is not understood.\n\nExamples\n--------\n\nSELECT INET_ATON('192.168.1.1');\n+--------------------------+\n| INET_ATON('192.168.1.1') |\n+--------------------------+\n| 3232235777 |\n+--------------------------+\n\nThis is calculated as follows: 192 x 2563 + 168 x 256 2 + 1 x 256 + 1\n\nURL: https://mariadb.com/kb/en/inet_aton/ [INET_NTOA] declaration=expr category=Miscellaneous Functions -description=Given a numeric IPv4 network address in network byte order\n(4 or 8 byte),\nreturns the dotted-quad representation of the address as a\nstring.\n \n\nSELECT INET_NTOA(3232235777);\n+-----------------------+\n| INET_NTOA(3232235777) |\n+-----------------------+\n| 192.168.1.1 |\n+-----------------------+\n \n192.168.1.1 corresponds to 3232235777 since 192 x 2563 + 168\nx 256 2 + 1 x 256 + 1 = 3232235777 +description=Given a numeric IPv4 network address in network byte order (4 or 8 byte),\nreturns the dotted-quad representation of the address as a string.\n\nExamples\n--------\n\nSELECT INET_NTOA(3232235777);\n+-----------------------+\n| INET_NTOA(3232235777) |\n+-----------------------+\n| 192.168.1.1 |\n+-----------------------+\n\n192.168.1.1 corresponds to 3232235777 since 192 x 2563 + 168 x 256 2 + 1 x 256\n+ 1 = 3232235777\n\nURL: https://mariadb.com/kb/en/inet_ntoa/ [INSTR] declaration=str,substr category=String Functions -description=Returns the position of the first occurrence of substring\nsubstr in\nstring str. This is the same as the two-argument form of\nLOCATE(),\nexcept that the order of the arguments is reversed.\n \nINSTR() performs a case-insensitive search.\n \nIf any argument is NULL, returns NULL.\n \n\nSELECT INSTR(''foobarbar'', ''bar'');\n+---------------------------+\n| INSTR(''foobarbar'', ''bar'') |\n+---------------------------+\n| 4 |\n+---------------------------+\n \nSELECT INSTR(''My'', ''Maria'');\n+----------------------+\n| INSTR(''My'', ''Maria'') |\n+----------------------+\n| 0 |\n+----------------------+ +description=Returns the position of the first occurrence of substring substr in string\nstr. This is the same as the two-argument form of LOCATE(), except that the\norder of the arguments is reversed.\n\nINSTR() performs a case-insensitive search.\n\nIf any argument is NULL, returns NULL.\n\nExamples\n--------\n\nSELECT INSTR('foobarbar', 'bar');\n+---------------------------+\n| INSTR('foobarbar', 'bar') |\n+---------------------------+\n| 4 |\n+---------------------------+\n\nSELECT INSTR('My', 'Maria');\n+----------------------+\n| INSTR('My', 'Maria') |\n+----------------------+\n| 0 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/instr/ +[INT] +declaration=M +category=Data Types +description=A normal-size integer. When marked UNSIGNED, it ranges from 0 to 4294967295,\notherwise its range is -2147483648 to 2147483647 (SIGNED is the default). If a\ncolumn has been set to ZEROFILL, all values will be prepended by zeros so that\nthe INT value contains a number of M digits. INTEGER is a synonym for INT.\n\nNote: If the ZEROFILL attribute has been specified, the column will\nautomatically become UNSIGNED.\n\nINT4 is a synonym for INT.\n\nFor details on the attributes, see Numeric Data Type Overview.\n\nExamples\n--------\n\nCREATE TABLE ints (a INT,b INT UNSIGNED,c INT ZEROFILL);\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO ints VALUES (-10,-10,-10);\nERROR 1264 (22003): Out of range value for column 'b' at row 1\n\nINSERT INTO ints VALUES (-10,10,-10);\nERROR 1264 (22003): Out of range value for column 'c' at row 1\n\nINSERT INTO ints VALUES (-10,10,10);\n\nINSERT INTO ints VALUES (2147483648,2147483648,2147483648);\nERROR 1264 (22003): Out of range value for column 'a' at row 1\n\nINSERT INTO ints VALUES (2147483647,2147483648,2147483648);\n\nSELECT * FROM ints;\n+------------+------------+------------+\n| a | b | c |\n+------------+------------+------------+\n| -10 | 10 | 0000000010 |\n| 2147483647 | 2147483648 | 2147483648 |\n+------------+------------+------------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO ints VALUES (-10,-10,-10);\nQuery OK, 1 row affected, 2 warnings (0.10 sec)\nWarning (Code 1264): Out of range value for column 'b' at row 1\nWarning (Code 1264): Out of range value for column 'c' at row 1\n\nINSERT INTO ints VALUES (-10,10,-10);\nQuery OK, 1 row affected, 1 warning (0.08 sec)\nWarning (Code 1264): Out of range value for column 'c' at row 1\n ... +[INTERSECT] +declaration=as well as EXCEPT +category=Data Manipulation +description=MariaDB 10.3.\n\nAll behavior for naming columns, ORDER BY and LIMIT is the same as for UNION.\n\nINTERSECT implicitly supposes a DISTINCT operation.\n\nThe result of an intersect is the intersection of right and left SELECT\nresults, i.e. only records that are present in both result sets will be\nincluded in the result of the operation.\n\nINTERSECT has higher precedence than UNION and EXCEPT (unless running running\nin Oracle mode, in which case all three have the same precedence). If possible\nit will be executed linearly but if not it will be translated to a subquery in\nthe FROM clause:\n\n(select a,b from t1)\nunion\n(select c,d from t2)\nintersect\n(select e,f from t3)\nunion\n(select 4,4);\n\nwill be translated to:\n\n(select a,b from t1)\nunion\nselect c,d from\n ((select c,d from t2)\n intersect\n (select e,f from t3)) dummy_subselect\nunion\n(select 4,4)\n\nMariaDB starting with 10.4.0\n----------------------------\n\nParentheses\n-----------\n\nFrom MariaDB 10.4.0, parentheses can be used to specify precedence. Before\nthis, a syntax error would be returned.\n\nMariaDB starting with 10.5.0\n----------------------------\n\nALL/DISTINCT\n------------\n\nINTERSECT ALL and INTERSECT DISTINCT were introduced in MariaDB 10.5.0. The\n ... [INTERSECTS] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether geometry g1 spatially\nintersects geometry g2.\n \nINTERSECTS() is based on the original MySQL implementation\nand uses object bounding rectangles, while ST_INTERSECTS()\nuses object shapes.\n \nINTERSECTS() tests the opposite relationship to DISJOINT(). +description=Returns 1 or 0 to indicate whether geometry g1 spatially intersects geometry\ng2.\n\nINTERSECTS() is based on the original MySQL implementation and uses object\nbounding rectangles, while ST_INTERSECTS() uses object shapes.\n\nINTERSECTS() tests the opposite relationship to DISJOINT().\n\nURL: https://mariadb.com/kb/en/intersects/ [INTERVAL] declaration=N,N1,N2,N3,... category=Comparison Operators -description=Returns the index of the last argument that is less than the\nfirst argument or is NULL. \n \nReturns 0 if N < N1, 1 if N < N2, 2 if N < N3 and so on or\n-1 if N is NULL. All\narguments are treated as integers. It is required that N1 <\nN2 < N3 +description=Returns the index of the last argument that is less than the first argument or\nis NULL.\n\nReturns 0 if N < N1, 1 if N < N2, 2 if N < N3 and so on or -1 if N is NULL.\nAll arguments are treated as integers. It is required that N1 < N2 < N3 < ...\n< Nn for this function to work correctly. This is because a fast binary search\nis used.\n\nExamples\n--------\n\nSELECT INTERVAL(23, 1, 15, 17, 30, 44, 200);\n+--------------------------------------+\n| INTERVAL(23, 1, 15, 17, 30, 44, 200) |\n+--------------------------------------+\n| 3 |\n+--------------------------------------+\n\nSELECT INTERVAL(10, 1, 10, 100, 1000);\n+--------------------------------+\n| INTERVAL(10, 1, 10, 100, 1000) |\n+--------------------------------+\n| 2 |\n+--------------------------------+\n\nSELECT INTERVAL(22, 23, 30, 44, 200);\n+-------------------------------+\n| INTERVAL(22, 23, 30, 44, 200) |\n+-------------------------------+\n| 0 |\n+-------------------------------+\n\nSELECT INTERVAL(10, 2, NULL);\n+-----------------------+\n| INTERVAL(10, 2, NULL) |\n+-----------------------+\n| 2 |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/interval/ [ISNULL] declaration=expr category=Comparison Operators -description=If expr is NULL, ISNULL() returns 1, otherwise it returns 0.\n \nSee also NULL Values in MariaDB.\n \n\nSELECT ISNULL(1+1);\n+-------------+\n| ISNULL(1+1) |\n+-------------+\n| 0 |\n+-------------+\n \nSELECT ISNULL(1/0);\n+-------------+\n| ISNULL(1/0) |\n+-------------+\n| 1 |\n+-------------+ +description=If expr is NULL, ISNULL() returns 1, otherwise it returns 0.\n\nSee also NULL Values in MariaDB.\n\nExamples\n--------\n\nSELECT ISNULL(1+1);\n+-------------+\n| ISNULL(1+1) |\n+-------------+\n| 0 |\n+-------------+\n\nSELECT ISNULL(1/0);\n+-------------+\n| ISNULL(1/0) |\n+-------------+\n| 1 |\n+-------------+\n\nURL: https://mariadb.com/kb/en/isnull/ [IS_FREE_LOCK] declaration=str category=Miscellaneous Functions -description=Checks whether the lock named str is free to use (that is,\nnot locked).\nReturns 1 if the lock is free (no one is using the lock),\n 0 if the lock is in use, and NULL if an\nerror occurs (such as an incorrect argument, like an empty\nstring or NULL). str is case insensitive.\n \nIf the metadata_lock_info plugin is installed, the\nInformation Schema metadata_lock_info table contains\ninformation about locks of this kind (as well as metadata\nlocks).\n \nStatements using the IS_FREE_LOCK() function are not safe\nfor replication. +description=Checks whether the lock named str is free to use (that is, not locked).\nReturns 1 if the lock is free (no one is using the lock), 0 if the lock is in\nuse, and NULL if an error occurs (such as an incorrect argument, like an empty\nstring or NULL). str is case insensitive.\n\nIf the metadata_lock_info plugin is installed, the Information Schema\nmetadata_lock_info table contains information about locks of this kind (as\nwell as metadata locks).\n\nStatements using the IS_FREE_LOCK function are not safe for statement-based\nreplication.\n\nURL: https://mariadb.com/kb/en/is_free_lock/ [IS_IPV4] declaration=expr category=Miscellaneous Functions -description=If the expression is a valid IPv4 address, returns 1,\notherwise returns 0.\n \nIS_IPV4() is stricter than INET_ATON(), but as strict as\nINET6_ATON(), in determining the validity of an IPv4\naddress. This implies that if IS_IPV4 returns 1, the same\nexpression will always return a non-NULL result when passed\nto INET_ATON(), but that the reverse may not apply.\n \n\nSELECT IS_IPV4(''1110.0.1.1'');\n+-----------------------+\n| IS_IPV4(''1110.0.1.1'') |\n+-----------------------+\n| 0 |\n+-----------------------+\n \nSELECT IS_IPV4(''48f3::d432:1431:ba23:846f'');\n+--------------------------------------+\n| IS_IPV4(''48f3::d432:1431:ba23:846f'') |\n+--------------------------------------+\n| 0 |\n+--------------------------------------+ +description=If the expression is a valid IPv4 address, returns 1, otherwise returns 0.\n\nIS_IPV4() is stricter than INET_ATON(), but as strict as INET6_ATON(), in\ndetermining the validity of an IPv4 address. This implies that if IS_IPV4\nreturns 1, the same expression will always return a non-NULL result when\npassed to INET_ATON(), but that the reverse may not apply.\n\nExamples\n--------\n\nSELECT IS_IPV4('1110.0.1.1');\n+-----------------------+\n| IS_IPV4('1110.0.1.1') |\n+-----------------------+\n| 0 |\n+-----------------------+\n\nSELECT IS_IPV4('48f3::d432:1431:ba23:846f');\n+--------------------------------------+\n| IS_IPV4('48f3::d432:1431:ba23:846f') |\n+--------------------------------------+\n| 0 |\n+--------------------------------------+\n\nURL: https://mariadb.com/kb/en/is_ipv4/ [IS_IPV4_COMPAT] declaration=expr category=Miscellaneous Functions -description=Returns 1 if a given numeric binary string IPv6 address,\nsuch as returned by INET6_ATON(), is IPv4-compatible,\notherwise returns 0. \n \n\nSELECT IS_IPV4_COMPAT(INET6_ATON(''::10.0.1.1''));\n+------------------------------------------+\n| IS_IPV4_COMPAT(INET6_ATON(''::10.0.1.1'')) |\n+------------------------------------------+\n| 1 |\n+------------------------------------------+\n \nSELECT\nIS_IPV4_COMPAT(INET6_ATON(''::48f3::d432:1431:ba23:846f''));\n+-----------------------------------------------------------+\n|\nIS_IPV4_COMPAT(INET6_ATON(''::48f3::d432:1431:ba23:846f''))\n|\n+-----------------------------------------------------------+\n| 0 |\n+-----------------------------------------------------------+ +description=Returns 1 if a given numeric binary string IPv6 address, such as returned by\nINET6_ATON(), is IPv4-compatible, otherwise returns 0.\n\nMariaDB starting with 10.5.0\n----------------------------\nFrom MariaDB 10.5.0, when the argument is not INET6, automatic implicit CAST\nto INET6 is applied. As a consequence, IS_IPV4_COMPAT now understands\narguments in both text representation and binary(16) representation. Before\nMariaDB 10.5.0, the function understood only binary(16) representation.\n\nExamples\n--------\n\nSELECT IS_IPV4_COMPAT(INET6_ATON('::10.0.1.1'));\n+------------------------------------------+\n| IS_IPV4_COMPAT(INET6_ATON('::10.0.1.1')) |\n+------------------------------------------+\n| 1 |\n+------------------------------------------+\n\nSELECT IS_IPV4_COMPAT(INET6_ATON('::48f3::d432:1431:ba23:846f'));\n+-----------------------------------------------------------+\n| IS_IPV4_COMPAT(INET6_ATON('::48f3::d432:1431:ba23:846f')) |\n+-----------------------------------------------------------+\n| 0 |\n+-----------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/is_ipv4_compat/ [IS_IPV4_MAPPED] declaration=expr category=Miscellaneous Functions -description=Returns 1 if a given a numeric binary string IPv6 address,\nsuch as returned by INET6_ATON(), is a valid IPv4-mapped\naddress, otherwise returns 0.\n \n\nSELECT IS_IPV4_MAPPED(INET6_ATON(''::10.0.1.1''));\n+------------------------------------------+\n| IS_IPV4_MAPPED(INET6_ATON(''::10.0.1.1'')) |\n+------------------------------------------+\n| 0 |\n+------------------------------------------+\n \nSELECT IS_IPV4_MAPPED(INET6_ATON(''::ffff:10.0.1.1''));\n+-----------------------------------------------+\n| IS_IPV4_MAPPED(INET6_ATON(''::ffff:10.0.1.1'')) |\n+-----------------------------------------------+\n| 1 |\n+-----------------------------------------------+ +description=Returns 1 if a given a numeric binary string IPv6 address, such as returned by\nINET6_ATON(), is a valid IPv4-mapped address, otherwise returns 0.\n\nMariaDB starting with 10.5.0\n----------------------------\nFrom MariaDB 10.5.0, when the argument is not INET6, automatic implicit CAST\nto INET6 is applied. As a consequence, IS_IPV4_MAPPED now understands\narguments in both text representation and binary(16) representation. Before\nMariaDB 10.5.0, the function understood only binary(16) representation.\n\nExamples\n--------\n\nSELECT IS_IPV4_MAPPED(INET6_ATON('::10.0.1.1'));\n+------------------------------------------+\n| IS_IPV4_MAPPED(INET6_ATON('::10.0.1.1')) |\n+------------------------------------------+\n| 0 |\n+------------------------------------------+\n\nSELECT IS_IPV4_MAPPED(INET6_ATON('::ffff:10.0.1.1'));\n+-----------------------------------------------+\n| IS_IPV4_MAPPED(INET6_ATON('::ffff:10.0.1.1')) |\n+-----------------------------------------------+\n| 1 |\n+-----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/is_ipv4_mapped/ [IS_IPV6] declaration=expr category=Miscellaneous Functions -description=Returns 1 if the expression is a valid IPv6 address\nspecified as a string, otherwise returns 0. Does not\nconsider IPv4 addresses to be valid IPv6 addresses.\n \n\n SELECT IS_IPV6(''48f3::d432:1431:ba23:846f'');\n+--------------------------------------+\n| IS_IPV6(''48f3::d432:1431:ba23:846f'') |\n+--------------------------------------+\n| 1 |\n+--------------------------------------+\n1 row in set (0.02 sec)\n \nSELECT IS_IPV6(''10.0.1.1'');\n+---------------------+\n| IS_IPV6(''10.0.1.1'') |\n+---------------------+\n| 0 |\n+---------------------+ +description=Returns 1 if the expression is a valid IPv6 address specified as a string,\notherwise returns 0. Does not consider IPv4 addresses to be valid IPv6\naddresses.\n\nExamples\n--------\n\nSELECT IS_IPV6('48f3::d432:1431:ba23:846f');\n+--------------------------------------+\n| IS_IPV6('48f3::d432:1431:ba23:846f') |\n+--------------------------------------+\n| 1 |\n+--------------------------------------+\n1 row in set (0.02 sec)\n\nSELECT IS_IPV6('10.0.1.1');\n+---------------------+\n| IS_IPV6('10.0.1.1') |\n+---------------------+\n| 0 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/is_ipv6/ [IS_USED_LOCK] declaration=str category=Miscellaneous Functions -description=Checks whether the lock named str is in use (that is,\nlocked). If so,\nit returns the connection identifier of the client that\nholds the\nlock. Otherwise, it returns NULL. str is case insensitive.\n \nIf the metadata_lock_info plugin is installed, the\nInformation Schema metadata_lock_info table contains\ninformation about locks of this kind (as well as metadata\nlocks).\n \nStatements using the IS_USED_LOCK() function are not safe\nfor replication. +description=Checks whether the lock named str is in use (that is, locked). If so, it\nreturns the connection identifier of the client that holds the lock.\nOtherwise, it returns NULL. str is case insensitive.\n\nIf the metadata_lock_info plugin is installed, the Information Schema\nmetadata_lock_info table contains information about locks of this kind (as\nwell as metadata locks).\n\nStatements using the IS_USED_LOCK function are not safe for statement-based\nreplication.\n\nURL: https://mariadb.com/kb/en/is_used_lock/ [JSON_ARRAY] declaration=[value[, value2] ...] category=JSON Functions -description=Returns a JSON array containing the listed values. The list\ncan be empty.\n \nExample\n \nSELECT Json_Array(56, 3.1416, ''My name is "Foo"'', NULL);\n+--------------------------------------------------+\n| Json_Array(56, 3.1416, ''My name is "Foo"'', NULL) |\n+--------------------------------------------------+\n| [56, 3.1416, "My name is \"Foo\"", null] |\n+--------------------------------------------------+ +description=Returns a JSON array containing the listed values. The list can be empty.\n\nExample\n-------\n\nSELECT Json_Array(56, 3.1416, 'My name is "Foo"', NULL);\n+--------------------------------------------------+\n| Json_Array(56, 3.1416, 'My name is "Foo"', NULL) |\n+--------------------------------------------------+\n| [56, 3.1416, "My name is \"Foo\"", null] |\n+--------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_array/ +[JSON_ARRAYAGG] +declaration=column_or_expression +category=JSON Functions +description=JSON_ARRAYAGG returns a JSON array containing an element for each value in a\ngiven set of JSON or SQL values. It acts on a column or an expression that\nevaluates to a single value.\n\nThe maximum returned length in bytes is determined by the group_concat_max_len\nserver system variable.\n\nReturns NULL in the case of an error, or if the result contains no rows.\n\nJSON_ARRAYAGG cannot currently be used as a window function.\n\nThe full syntax is as follows:\n\nJSON_ARRAYAGG([DISTINCT] expr\n [ORDER BY {unsigned_integer | col_name | expr}\n [ASC | DESC] [,col_name ...]]\n [LIMIT {[offset,] row_count | row_count OFFSET offset}])\n\nExamples\n--------\n\nCREATE TABLE t1 (a INT, b INT);\n\nINSERT INTO t1 VALUES (1, 1),(2, 1), (1, 1),(2, 1), (3, 2),(2, 2),(2, 2),(2,\n2);\n\nSELECT JSON_ARRAYAGG(a), JSON_ARRAYAGG(b) FROM t1;\n+-------------------+-------------------+\n| JSON_ARRAYAGG(a) | JSON_ARRAYAGG(b) |\n+-------------------+-------------------+\n| [1,2,1,2,3,2,2,2] | [1,1,1,1,2,2,2,2] |\n+-------------------+-------------------+\n\nSELECT JSON_ARRAYAGG(a), JSON_ARRAYAGG(b) FROM t1 GROUP BY b;\n+------------------+------------------+\n| JSON_ARRAYAGG(a) | JSON_ARRAYAGG(b) |\n+------------------+------------------+\n| [1,2,1,2] | [1,1,1,1] |\n| [3,2,2,2] | [2,2,2,2] |\n+------------------+------------------+\n\nURL: https://mariadb.com/kb/en/json_arrayagg/ [JSON_ARRAY_APPEND] declaration=json_doc, path, value[, path, value] ... category=JSON Functions -description=Appends values to the end of the specified arrays within a\nJSON document, returning the result, or NULL if any of the\narguments are NULL.\n \nEvaluation is performed from left to right, with the\nresulting document from the previous pair becoming the new\nvalue against which the next pair is evaluated.\n \nIf the json_doc is not a valid JSON document, or if any of\nthe paths are not valid, or contain a * or ** wildcard, an\nerror is returned.\n \n\nSET @json = ''[1, 2, [3, 4]]'';\n \nSELECT JSON_ARRAY_APPEND(@json, ''$[0]'', 5)\n+-------------------------------------+\n| JSON_ARRAY_APPEND(@json, ''$[0]'', 5) |\n+-------------------------------------+\n| [[1, 5], 2, [3, 4]] |\n+-------------------------------------+\n \nSELECT JSON_ARRAY_APPEND(@json, ''$[1]'', 6);\n+-------------------------------------+\n| JSON_ARRAY_APPEND(@json, ''$[1]'', 6) |\n+-------------------------------------+\n| [1, [2, 6], [3, 4]] |\n+-------------------------------------+\n \nSELECT JSON_ARRAY_APPEND(@json, ''$[1]'', 6, ''$[2]'', 7);\n+------------------------------------------------+\n| JSON_ARRAY_APPEND(@json, ''$[1]'', 6, ''$[2]'', 7) |\n+------------------------------------------------+\n| [1, [2, 6], [3, 4, 7]] |\n+------------------------------------------------+\n \nSELECT JSON_ARRAY_APPEND(@json, ''$'', 5);\n+----------------------------------+\n| JSON_ARRAY_APPEND(@json, ''$'', 5) |\n+----------------------------------+\n| [1, 2, [3, 4], 5] |\n+----------------------------------+\n \nSET @json = ''{"A": 1, "B": [2], "C": [3, 4]}'';\n \nSELECT JSON_ARRAY_APPEND(@json, ''$.B'', 5);\n+------------------------------------+\n| JSON_ARRAY_APPEND(@json, ''$.B'', 5) |\n+------------------------------------+\n| {"A": 1, "B": [2, 5], "C": [3, 4]} |\n+------------------------------------+ +description=Appends values to the end of the specified arrays within a JSON document,\nreturning the result, or NULL if any of the arguments are NULL.\n\nEvaluation is performed from left to right, with the resulting document from\nthe previous pair becoming the new value against which the next pair is\nevaluated.\n\nIf the json_doc is not a valid JSON document, or if any of the paths are not\nvalid, or contain a * or ** wildcard, an error is returned.\n\nExamples\n--------\n\nSET @json = '[1, 2, [3, 4]]';\n\nSELECT JSON_ARRAY_APPEND(@json, '$[0]', 5)\n+-------------------------------------+\n| JSON_ARRAY_APPEND(@json, '$[0]', 5) |\n+-------------------------------------+\n| [[1, 5], 2, [3, 4]] |\n+-------------------------------------+\n\nSELECT JSON_ARRAY_APPEND(@json, '$[1]', 6);\n+-------------------------------------+\n| JSON_ARRAY_APPEND(@json, '$[1]', 6) |\n+-------------------------------------+\n| [1, [2, 6], [3, 4]] |\n+-------------------------------------+\n\nSELECT JSON_ARRAY_APPEND(@json, '$[1]', 6, '$[2]', 7);\n+------------------------------------------------+\n| JSON_ARRAY_APPEND(@json, '$[1]', 6, '$[2]', 7) |\n+------------------------------------------------+\n| [1, [2, 6], [3, 4, 7]] |\n+------------------------------------------------+\n\nSELECT JSON_ARRAY_APPEND(@json, '$', 5);\n+----------------------------------+\n| JSON_ARRAY_APPEND(@json, '$', 5) |\n+----------------------------------+\n| [1, 2, [3, 4], 5] |\n+----------------------------------+\n\nSET @json = '{"A": 1, "B": [2], "C": [3, 4]}';\n\nSELECT JSON_ARRAY_APPEND(@json, '$.B', 5);\n+------------------------------------+\n| JSON_ARRAY_APPEND(@json, '$.B', 5) |\n+------------------------------------+\n| {"A": 1, "B": [2, 5], "C": [3, 4]} |\n+------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_array_append/ [JSON_ARRAY_INSERT] declaration=json_doc, path, value[, path, value] ... category=JSON Functions -description=Inserts a value into a JSON document, returning the modified\ndocument, or NULL if any of the arguments are NULL.\n \nEvaluation is performed from left to right, with the\nresulting document from the previous pair becoming the new\nvalue against which the next pair is evaluated.\n \nIf the json_doc is not a valid JSON document, or if any of\nthe paths are not valid, or contain a * or ** wildcard, an\nerror is returned.\n \n\nSET @json = ''[1, 2, [3, 4]]'';\n \nSELECT JSON_ARRAY_INSERT(@json, ''$[0]'', 5);\n+-------------------------------------+\n| JSON_ARRAY_INSERT(@json, ''$[0]'', 5) |\n+-------------------------------------+\n| [5, 1, 2, [3, 4]] |\n+-------------------------------------+\n \nSELECT JSON_ARRAY_INSERT(@json, ''$[1]'', 6);\n+-------------------------------------+\n| JSON_ARRAY_INSERT(@json, ''$[1]'', 6) |\n+-------------------------------------+\n| [1, 6, 2, [3, 4]] |\n+-------------------------------------+\n \nSELECT JSON_ARRAY_INSERT(@json, ''$[1]'', 6, ''$[2]'', 7);\n+------------------------------------------------+\n| JSON_ARRAY_INSERT(@json, ''$[1]'', 6, ''$[2]'', 7) |\n+------------------------------------------------+\n| [1, 6, 7, 2, [3, 4]] |\n+------------------------------------------------+ +description=Inserts a value into a JSON document, returning the modified document, or NULL\nif any of the arguments are NULL.\n\nEvaluation is performed from left to right, with the resulting document from\nthe previous pair becoming the new value against which the next pair is\nevaluated.\n\nIf the json_doc is not a valid JSON document, or if any of the paths are not\nvalid, or contain a * or ** wildcard, an error is returned.\n\nExamples\n--------\n\nSET @json = '[1, 2, [3, 4]]';\n\nSELECT JSON_ARRAY_INSERT(@json, '$[0]', 5);\n+-------------------------------------+\n| JSON_ARRAY_INSERT(@json, '$[0]', 5) |\n+-------------------------------------+\n| [5, 1, 2, [3, 4]] |\n+-------------------------------------+\n\nSELECT JSON_ARRAY_INSERT(@json, '$[1]', 6);\n+-------------------------------------+\n| JSON_ARRAY_INSERT(@json, '$[1]', 6) |\n+-------------------------------------+\n| [1, 6, 2, [3, 4]] |\n+-------------------------------------+\n\nSELECT JSON_ARRAY_INSERT(@json, '$[1]', 6, '$[2]', 7);\n+------------------------------------------------+\n| JSON_ARRAY_INSERT(@json, '$[1]', 6, '$[2]', 7) |\n+------------------------------------------------+\n| [1, 6, 7, 2, [3, 4]] |\n+------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_array_insert/ +[JSON_ARRAY_INTERSECT] +declaration=arr1, arr2 +category=JSON Functions +description=Finds intersection between two json arrays and returns an array of items found\nin both array.\n\nExamples\n--------\n\nSET @json1= '[1,2,3]';\nSET @json2= '[1,2,4]';\n\nSELECT json_array_intersect(@json1, @json2); \n+--------------------------------------+\n| json_array_intersect(@json1, @json2) |\n+--------------------------------------+\n| [1, 2] |\n+--------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_array_intersect/ [JSON_COMPACT] declaration=json_doc category=JSON Functions -description=Removes all unnecessary spaces so the json document is as\nshort as possible.\n \nExample\n \nSET @j = ''{ "A": 1, "B": [2, 3]}'';\n \nSELECT JSON_COMPACT(@j), @j;\n+-------------------+------------------------+\n| JSON_COMPACT(@j) | @j |\n+-------------------+------------------------+\n| {"A":1,"B":[2,3]} | { "A": 1, "B": [2, 3]} |\n+-------------------+------------------------+ +description=Removes all unnecessary spaces so the json document is as short as possible.\n\nExample\n-------\n\nSET @j = '{ "A": 1, "B": [2, 3]}';\n\nSELECT JSON_COMPACT(@j), @j;\n+-------------------+------------------------+\n| JSON_COMPACT(@j) | @j |\n+-------------------+------------------------+\n| {"A":1,"B":[2,3]} | { "A": 1, "B": [2, 3]} |\n+-------------------+------------------------+\n\nURL: https://mariadb.com/kb/en/json_compact/ [JSON_CONTAINS] declaration=json_doc, val[, path] category=JSON Functions -description=Returns whether or not the specified value is found in the\ngiven JSON document or, optionally, at the specified path\nwithin the document. Returns 1 if it does, 0 if not and NULL\nif any of the arguments are null. An error occurs if the\ndocument or path is not valid, or contains the * or **\nwildcards.\n \n\nSET @json = ''{"A": 0, "B": {"C": 1}, "D": 2}'';\n \nSELECT JSON_CONTAINS(@json, ''2'', ''$.A'');\n+----------------------------------+\n| JSON_CONTAINS(@json, ''2'', ''$.A'') |\n+----------------------------------+\n| 0 |\n+----------------------------------+\n \nSELECT JSON_CONTAINS(@json, ''2'', ''$.D'');\n+----------------------------------+\n| JSON_CONTAINS(@json, ''2'', ''$.D'') |\n+----------------------------------+\n| 1 |\n+----------------------------------+\n \nSELECT JSON_CONTAINS(@json, ''{"C": 1}'', ''$.A'');\n+-----------------------------------------+\n| JSON_CONTAINS(@json, ''{"C": 1}'', ''$.A'') |\n+-----------------------------------------+\n| 0 |\n+-----------------------------------------+\n \nSELECT JSON_CONTAINS(@json, ''{"C": 1}'', ''$.B'');\n+-----------------------------------------+\n| JSON_CONTAINS(@json, ''{"C": 1}'', ''$.B'') |\n+-----------------------------------------+\n| 1 |\n+-----------------------------------------+ +description=Returns whether or not the specified value is found in the given JSON document\nor, optionally, at the specified path within the document. Returns 1 if it\ndoes, 0 if not and NULL if any of the arguments are null. An error occurs if\nthe document or path is not valid, or contains the * or ** wildcards.\n\nExamples\n--------\n\nSET @json = '{"A": 0, "B": {"C": 1}, "D": 2}';\n\nSELECT JSON_CONTAINS(@json, '2', '$.A');\n+----------------------------------+\n| JSON_CONTAINS(@json, '2', '$.A') |\n+----------------------------------+\n| 0 |\n+----------------------------------+\n\nSELECT JSON_CONTAINS(@json, '2', '$.D');\n+----------------------------------+\n| JSON_CONTAINS(@json, '2', '$.D') |\n+----------------------------------+\n| 1 |\n+----------------------------------+\n\nSELECT JSON_CONTAINS(@json, '{"C": 1}', '$.A');\n+-----------------------------------------+\n| JSON_CONTAINS(@json, '{"C": 1}', '$.A') |\n+-----------------------------------------+\n| 0 |\n+-----------------------------------------+\n\nSELECT JSON_CONTAINS(@json, '{"C": 1}', '$.B');\n+-----------------------------------------+\n| JSON_CONTAINS(@json, '{"C": 1}', '$.B') |\n+-----------------------------------------+\n| 1 |\n+-----------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_contains/ [JSON_CONTAINS_PATH] declaration=json_doc, return_arg, path[, path] ... category=JSON Functions -description=Indicates whether the given JSON document contains data at\nthe specified path or paths. Returns 1 if it does, 0 if not\nand NULL if any of the arguments are null.\n \nThe return_arg can be one or all:\none - Returns 1 if at least one path exists within the JSON\ndocument. \nall - Returns 1 only if all paths exist within the JSON\ndocument.\n \n\nSET @json = ''{"A": 1, "B": [2], "C": [3, 4]}'';\n \nSELECT JSON_CONTAINS_PATH(@json, ''one'', ''$.A'', ''$.D'');\n+------------------------------------------------+\n| JSON_CONTAINS_PATH(@json, ''one'', ''$.A'', ''$.D'') |\n+------------------------------------------------+\n| 1 |\n+------------------------------------------------+\n1 row in set (0.00 sec)\n \nSELECT JSON_CONTAINS_PATH(@json, ''all'', ''$.A'', ''$.D'');\n+------------------------------------------------+\n| JSON_CONTAINS_PATH(@json, ''all'', ''$.A'', ''$.D'') |\n+------------------------------------------------+\n| 0 |\n+------------------------------------------------+ +description=Indicates whether the given JSON document contains data at the specified path\nor paths. Returns 1 if it does, 0 if not and NULL if any of the arguments are\nnull.\n\nThe return_arg can be one or all:\n\n* one - Returns 1 if at least one path exists within the JSON document. \n* all - Returns 1 only if all paths exist within the JSON document.\n\nExamples\n--------\n\nSET @json = '{"A": 1, "B": [2], "C": [3, 4]}';\n\nSELECT JSON_CONTAINS_PATH(@json, 'one', '$.A', '$.D');\n+------------------------------------------------+\n| JSON_CONTAINS_PATH(@json, 'one', '$.A', '$.D') |\n+------------------------------------------------+\n| 1 |\n+------------------------------------------------+\n1 row in set (0.00 sec)\n\nSELECT JSON_CONTAINS_PATH(@json, 'all', '$.A', '$.D');\n+------------------------------------------------+\n| JSON_CONTAINS_PATH(@json, 'all', '$.A', '$.D') |\n+------------------------------------------------+\n| 0 |\n+------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_contains_path/ [JSON_DEPTH] declaration=json_doc category=JSON Functions -description=Returns the maximum depth of the given JSON document, or\nNULL if the argument is null. An error will occur if the\nargument is an invalid JSON document.\nScalar values or empty arrays or objects have a depth of 1.\nArrays or objects that are not empty but contain only\nelements or member values of depth 1 will have a depth of 2.\nIn other cases, the depth will be greater than 2.\n \n\nSELECT JSON_DEPTH(''[]''), JSON_DEPTH(''true''),\nJSON_DEPTH(''{}'');\n+------------------+--------------------+------------------+\n| JSON_DEPTH(''[]'') | JSON_DEPTH(''true'') |\nJSON_DEPTH(''{}'') |\n+------------------+--------------------+------------------+\n| 1 | 1 | 1 |\n+------------------+--------------------+------------------+\n \nSELECT JSON_DEPTH(''[1, 2, 3]''), JSON_DEPTH(''[[], {},\n[]]'');\n+-------------------------+----------------------------+\n| JSON_DEPTH(''[1, 2, 3]'') | JSON_DEPTH(''[[], {}, []]'') |\n+-------------------------+----------------------------+\n| 2 | 2 |\n+-------------------------+----------------------------+\n \nSELECT JSON_DEPTH(''[1, 2, [3, 4, 5, 6], 7]'');\n+---------------------------------------+\n| JSON_DEPTH(''[1, 2, [3, 4, 5, 6], 7]'') |\n+---------------------------------------+\n| 3 |\n+---------------------------------------+ +description=Returns the maximum depth of the given JSON document, or NULL if the argument\nis null. An error will occur if the argument is an invalid JSON document.\n\n* Scalar values or empty arrays or objects have a depth of 1.\n* Arrays or objects that are not empty but contain only elements or member\nvalues of depth 1 will have a depth of 2.\n* In other cases, the depth will be greater than 2.\n\nExamples\n--------\n\nSELECT JSON_DEPTH('[]'), JSON_DEPTH('true'), JSON_DEPTH('{}');\n+------------------+--------------------+------------------+\n| JSON_DEPTH('[]') | JSON_DEPTH('true') | JSON_DEPTH('{}') |\n+------------------+--------------------+------------------+\n| 1 | 1 | 1 |\n+------------------+--------------------+------------------+\n\nSELECT JSON_DEPTH('[1, 2, 3]'), JSON_DEPTH('[[], {}, []]');\n+-------------------------+----------------------------+\n| JSON_DEPTH('[1, 2, 3]') | JSON_DEPTH('[[], {}, []]') |\n+-------------------------+----------------------------+\n| 2 | 2 |\n+-------------------------+----------------------------+\n\nSELECT JSON_DEPTH('[1, 2, [3, 4, 5, 6], 7]');\n+---------------------------------------+\n| JSON_DEPTH('[1, 2, [3, 4, 5, 6], 7]') |\n+---------------------------------------+\n| 3 |\n+---------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_depth/ [JSON_DETAILED] declaration=json_doc[, tab_size] category=JSON Functions -description=Represents JSON in the most understandable way emphasizing\nnested structures.\n \nExample\n \nSET @j = ''{ "A":1,"B":[2,3]}'';\n \nSELECT @j;\n+--------------------+\n| @j |\n+--------------------+\n| { "A":1,"B":[2,3]} |\n+--------------------+\n \nSELECT JSON_DETAILED(@j);\n+------------------------------------------------------------+\n| JSON_DETAILED(@j) |\n+------------------------------------------------------------+\n| {\n "A": 1,\n "B": \n [\n 2,\n 3\n ]\n} |\n+------------------------------------------------------------+ +description=Represents JSON in the most understandable way emphasizing nested structures.\n\nJSON_PRETTY was added as an alias for JSON_DETAILED in MariaDB 10.10.3,\nMariaDB 10.9.5, MariaDB 10.8.7, MariaDB 10.7.8, MariaDB 10.6.12, MariaDB\n10.5.19 and MariaDB 10.4.28.\n\nExample\n-------\n\nSET @j = '{ "A":1,"B":[2,3]}';\n\nSELECT @j;\n+--------------------+\n| @j |\n+--------------------+\n| { "A":1,"B":[2,3]} |\n+--------------------+\n\nSELECT JSON_DETAILED(@j);\n+------------------------------------------------------------+\n| JSON_DETAILED(@j) |\n+------------------------------------------------------------+\n| {\n "A": 1,\n "B":\n [\n 2,\n 3\n ]\n} |\n+------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_detailed/ +[JSON_EQUALS] +declaration=json1, json2 +category=JSON Functions +description=Checks if there is equality between two json objects. Returns 1 if it there\nis, 0 if not, or NULL if any of the arguments are null.\n\nExamples\n--------\n\nSELECT JSON_EQUALS('{"a" :[1, 2, 3],"b":[4]}', '{"b":[4],"a":[1, 2, 3.0]}');\n+------------------------------------------------------------------------+\n| JSON_EQUALS('{"a" :[1, 2, 3],"b":[4]}', '{"b":[4],"a":[1, 2, 3.0]}') |\n+------------------------------------------------------------------------+\n| 1 |\n+------------------------------------------------------------------------+\n\nSELECT JSON_EQUALS('{"a":[1, 2, 3]}', '{"a":[1, 2, 3.01]}');\n+------------------------------------------------------+\n| JSON_EQUALS('{"a":[1, 2, 3]}', '{"a":[1, 2, 3.01]}') |\n+------------------------------------------------------+\n| 0 |\n+------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_equals/ +[JSON_EXISTS] +declaration='{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2" +category=JSON Functions +description=+------------------------------------------------------------+\n| JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2") |\n+------------------------------------------------------------+\n| 1 |\n+------------------------------------------------------------+\n\nSELECT JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key3");\n+------------------------------------------------------------+\n| JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key3") |\n+------------------------------------------------------------+\n| 0 |\n+------------------------------------------------------------+\n\nSELECT JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[1]");\n+---------------------------------------------------------------+\n| JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[1]") |\n+---------------------------------------------------------------+\n| 1 |\n+---------------------------------------------------------------+\n\nSELECT JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[10]");\n+----------------------------------------------------------------+\n| JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[10]") |\n+----------------------------------------------------------------+\n| 0 |\n+----------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_exists/ [JSON_EXTRACT] declaration=json_doc, path[, path] ... category=JSON Functions -description=Extracts data from a JSON document. The extracted data is\nselected from the parts matching the path arguments. Returns\nall matched values; either as a single matched value, or, if\nthe arguments could return multiple values, a result\nautowrapped as an array in the matching order.\n \nReturns NULL if no paths match or if any of the arguments\nare NULL. \n \nAn error will occur if any path argument is not a valid\npath, or if the json_doc argument is not a valid JSON\ndocument.\n \n\nSET @json = ''[1, 2, [3, 4]]'';\n \nSELECT JSON_EXTRACT(@json, ''$[1]'');\n+-----------------------------+\n| JSON_EXTRACT(@json, ''$[1]'') |\n+-----------------------------+\n| 2 |\n+-----------------------------+\n \nSELECT JSON_EXTRACT(@json, ''$[2]'');\n+-----------------------------+\n| JSON_EXTRACT(@json, ''$[2]'') |\n+-----------------------------+\n| [3, 4] |\n+-----------------------------+\n \nSELECT JSON_EXTRACT(@json, ''$[2][1]'');\n+--------------------------------+\n| JSON_EXTRACT(@json, ''$[2][1]'') |\n+--------------------------------+\n| 4 |\n+--------------------------------+ +description=Extracts data from a JSON document. The extracted data is selected from the\nparts matching the path arguments. Returns all matched values; either as a\nsingle matched value, or, if the arguments could return multiple values, a\nresult autowrapped as an array in the matching order.\n\nReturns NULL if no paths match or if any of the arguments are NULL.\n\nAn error will occur if any path argument is not a valid path, or if the\njson_doc argument is not a valid JSON document.\n\nThe path expression be a JSONPath expression as supported by MariaDB\n\nExamples\n--------\n\nSET @json = '[1, 2, [3, 4]]';\n\nSELECT JSON_EXTRACT(@json, '$[1]');\n+-----------------------------+\n| JSON_EXTRACT(@json, '$[1]') |\n+-----------------------------+\n| 2 |\n+-----------------------------+\n\nSELECT JSON_EXTRACT(@json, '$[2]');\n+-----------------------------+\n| JSON_EXTRACT(@json, '$[2]') |\n+-----------------------------+\n| [3, 4] |\n+-----------------------------+\n\nSELECT JSON_EXTRACT(@json, '$[2][1]');\n+--------------------------------+\n| JSON_EXTRACT(@json, '$[2][1]') |\n+--------------------------------+\n| 4 |\n+--------------------------------+\n\nURL: https://mariadb.com/kb/en/json_extract/ [JSON_INSERT] declaration=json_doc, path, val[, path, val] ... category=JSON Functions -description=Inserts data into a JSON document, returning the resulting\ndocument or NULL if any argument is null. \n \nAn error will occur if the JSON document is not invalid, or\nif any of the paths are invalid or contain a * or **\nwildcard.\n \nJSON_INSERT can only insert data while JSON_REPLACE can only\nupdate. JSON_SET can update or insert data. \n \n\nSET @json = ''{ "A": 0, "B": [1, 2]}'';\n \nSELECT JSON_INSERT(@json, ''$.C'', ''[3, 4]'');\n+--------------------------------------+\n| JSON_INSERT(@json, ''$.C'', ''[3, 4]'') |\n+--------------------------------------+\n| { "A": 0, "B": [1, 2], "C":"[3, 4]"} |\n+--------------------------------------+ +description=Inserts data into a JSON document, returning the resulting document or NULL if\neither of the json_doc or path arguments are null.\n\nAn error will occur if the JSON document is invalid, or if any of the paths\nare invalid or contain a * or ** wildcard.\n\nJSON_INSERT can only insert data while JSON_REPLACE can only update. JSON_SET\ncan update or insert data.\n\nExamples\n--------\n\nSET @json = '{ "A": 0, "B": [1, 2]}';\n\nSELECT JSON_INSERT(@json, '$.C', '[3, 4]');\n+--------------------------------------+\n| JSON_INSERT(@json, '$.C', '[3, 4]') |\n+--------------------------------------+\n| { "A": 0, "B": [1, 2], "C":"[3, 4]"} |\n+--------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_insert/ [JSON_KEYS] declaration=json_doc[, path] category=JSON Functions -description=Returns the keys as a JSON array from the top-level value of\na JSON object or, if the optional path argument is provided,\nthe top-level keys from the path. \n \nExcludes keys from nested sub-objects in the top level\nvalue. The resulting array will be empty if the selected\nobject is empty.\n \nReturns NULL if any of the arguments are null, a given path\ndoes not locate an object, or if the json_doc argument is\nnot an object.\n \nAn error will occur if JSON document is invalid, the path is\ninvalid or if the path contains a * or ** wildcard.\n \n\nSELECT JSON_KEYS(''{"A": 1, "B": {"C": 2}}'');\n+--------------------------------------+\n| JSON_KEYS(''{"A": 1, "B": {"C": 2}}'') |\n+--------------------------------------+\n| ["A", "B"] |\n+--------------------------------------+\n \nSELECT JSON_KEYS(''{"A": 1, "B": 2, "C": {"D":\n3}}'', ''$.C'');\n+-----------------------------------------------------+\n| JSON_KEYS(''{"A": 1, "B": 2, "C": {"D": 3}}'',\n''$.C'') |\n+-----------------------------------------------------+\n| ["D"] |\n+-----------------------------------------------------+ +description=Returns the keys as a JSON array from the top-level value of a JSON object or,\nif the optional path argument is provided, the top-level keys from the path.\n\nExcludes keys from nested sub-objects in the top level value. The resulting\narray will be empty if the selected object is empty.\n\nReturns NULL if any of the arguments are null, a given path does not locate an\nobject, or if the json_doc argument is not an object.\n\nAn error will occur if JSON document is invalid, the path is invalid or if the\npath contains a * or ** wildcard.\n\nExamples\n--------\n\nSELECT JSON_KEYS('{"A": 1, "B": {"C": 2}}');\n+--------------------------------------+\n| JSON_KEYS('{"A": 1, "B": {"C": 2}}') |\n+--------------------------------------+\n| ["A", "B"] |\n+--------------------------------------+\n\nSELECT JSON_KEYS('{"A": 1, "B": 2, "C": {"D": 3}}', '$.C');\n+-----------------------------------------------------+\n| JSON_KEYS('{"A": 1, "B": 2, "C": {"D": 3}}', '$.C') |\n+-----------------------------------------------------+\n| ["D"] |\n+-----------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_keys/ [JSON_LENGTH] declaration=json_doc[, path] category=JSON Functions -description=Returns the length of a JSON document, or, if the optional\npath argument is given, the length of the value within the\ndocument specified by the path. \n \nReturns NULL if any of the arguments argument are null or\nthe path argument does not identify a value in the document.\n\n \nAn error will occur if the JSON document is invalid, the\npath is invalid or if the path contains a * or ** wildcard.\n \nLength will be determined as follow:\nA scalar''s length is always 1.\nIf an array, the number of elements in the array.\nIf an object, the number of members in the object.\n \nThe length of nested arrays or objects are not counted. +description=Returns the length of a JSON document, or, if the optional path argument is\ngiven, the length of the value within the document specified by the path.\n\nReturns NULL if any of the arguments argument are null or the path argument\ndoes not identify a value in the document.\n\nAn error will occur if the JSON document is invalid, the path is invalid or if\nthe path contains a * or ** wildcard.\n\nLength will be determined as follow:\n\n* A scalar's length is always 1.\n* If an array, the number of elements in the array.\n* If an object, the number of members in the object.\n\nThe length of nested arrays or objects are not counted.\n\nExamples\n--------\n\nURL: https://mariadb.com/kb/en/json_length/ [JSON_LOOSE] declaration=json_doc category=JSON Functions -description=Adds spaces to a JSON document to make it look more\nreadable.\n \nExample\n \nSET @j = ''{ "A":1,"B":[2,3]}'';\n \nSELECT JSON_LOOSE(@j), @j;\n+-----------------------+--------------------+\n| JSON_LOOSE(@j) | @j |\n+-----------------------+--------------------+\n| {"A": 1, "B": [2, 3]} | { "A":1,"B":[2,3]} |\n+-----------------------+--------------------+ +description=Adds spaces to a JSON document to make it look more readable.\n\nExample\n-------\n\nSET @j = '{ "A":1,"B":[2,3]}';\n\nSELECT JSON_LOOSE(@j), @j;\n+-----------------------+--------------------+\n| JSON_LOOSE(@j) | @j |\n+-----------------------+--------------------+\n| {"A": 1, "B": [2, 3]} | { "A":1,"B":[2,3]} |\n+-----------------------+--------------------+\n\nURL: https://mariadb.com/kb/en/json_loose/ [JSON_MERGE] declaration=json_doc, json_doc[, json_doc] ... category=JSON Functions -description=Merges the given JSON documents.\n \nReturns the merged result,or NULL if any argument is NULL.\n \nAn error occurs if any of the arguments are not valid JSON\ndocuments.\n \nExample\n \nSET @json1 = ''[1, 2]'';\n \nSET @json2 = ''[3, 4]'';\n \nSELECT JSON_MERGE(@json1,@json2);\n+---------------------------+\n| JSON_MERGE(@json1,@json2) |\n+---------------------------+\n| [1, 2, 3, 4] |\n+---------------------------+ +description=Merges the given JSON documents.\n\nReturns the merged result,or NULL if any argument is NULL.\n\nAn error occurs if any of the arguments are not valid JSON documents.\n\nJSON_MERGE has been deprecated since MariaDB 10.2.25, MariaDB 10.3.16 and\nMariaDB 10.4.5. JSON_MERGE_PATCH is an RFC 7396-compliant replacement, and\nJSON_MERGE_PRESERVE is a synonym.\n\nExample\n-------\n\nSET @json1 = '[1, 2]';\nSET @json2 = '[3, 4]';\n\nSELECT JSON_MERGE(@json1,@json2);\n+---------------------------+\n| JSON_MERGE(@json1,@json2) |\n+---------------------------+\n| [1, 2, 3, 4] |\n+---------------------------+\n\nURL: https://mariadb.com/kb/en/json_merge/ +[JSON_MERGE_PATCH] +declaration=json_doc, json_doc[, json_doc] ... +category=JSON Functions +description=Merges the given JSON documents, returning the merged result, or NULL if any\nargument is NULL.\n\nJSON_MERGE_PATCH is an RFC 7396-compliant replacement for JSON_MERGE, which\nhas been deprecated.\n\nUnlike JSON_MERGE_PRESERVE, members with duplicate keys are not preserved.\n\nExample\n-------\n\nSET @json1 = '[1, 2]';\nSET @json2 = '[2, 3]';\nSELECT JSON_MERGE_PATCH(@json1,@json2),JSON_MERGE_PRESERVE(@json1,@json2);\n+---------------------------------+------------------------------------+\n| JSON_MERGE_PATCH(@json1,@json2) | JSON_MERGE_PRESERVE(@json1,@json2) |\n+---------------------------------+------------------------------------+\n| [2, 3] | [1, 2, 2, 3] |\n+---------------------------------+------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_merge_patch/ +[JSON_MERGE_PRESERVE] +declaration=json_doc, json_doc[, json_doc] ... +category=JSON Functions +description=Merges the given JSON documents, returning the merged result, or NULL if any\nargument is NULL.\n\nJSON_MERGE_PRESERVE was introduced as a synonym for JSON_MERGE, which has been\ndeprecated.\n\nUnlike JSON_MERGE_PATCH, members with duplicate keys are preserved.\n\nExample\n-------\n\nSET @json1 = '[1, 2]';\nSET @json2 = '[2, 3]';\nSELECT JSON_MERGE_PATCH(@json1,@json2),JSON_MERGE_PRESERVE(@json1,@json2);\n+---------------------------------+------------------------------------+\n| JSON_MERGE_PATCH(@json1,@json2) | JSON_MERGE_PRESERVE(@json1,@json2) |\n+---------------------------------+------------------------------------+\n| [2, 3] | [1, 2, 2, 3] |\n+---------------------------------+------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_merge_preserve/ +[JSON_NORMALIZE] +declaration=json +category=JSON Functions +description=Recursively sorts keys and removes spaces, allowing comparison of json\ndocuments for equality.\n\nExamples\n--------\n\nWe may wish our application to use the database to enforce a unique constraint\non the JSON contents, and we can do so using the JSON_NORMALIZE function in\ncombination with a unique key.\n\nFor example, if we have a table with a JSON column:\n\nCREATE TABLE t1 (\n id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,\n val JSON,\n /* other columns here */\n PRIMARY KEY (id)\n);\n\nAdd a unique constraint using JSON_NORMALIZE like this:\n\nALTER TABLE t1\n ADD COLUMN jnorm JSON AS (JSON_NORMALIZE(val)) VIRTUAL,\n ADD UNIQUE KEY (jnorm);\n\nWe can test this by first inserting a row as normal:\n\nINSERT INTO t1 (val) VALUES ('{"name":"alice","color":"blue"}');\n\nAnd then seeing what happens with a different string which would produce the\nsame JSON object:\n\nINSERT INTO t1 (val) VALUES ('{ "color": "blue", "name": "alice" }');\nERROR 1062 (23000): Duplicate entry '{"color":"blue","name":"alice"}' for key\n'jnorm'\n\nURL: https://mariadb.com/kb/en/json_normalize/ [JSON_OBJECT] declaration=[key, value[, key, value] ...] category=JSON Functions -description=Returns a JSON object containing the given key/value pairs.\nThe key/value list can be empty.\n \nAn error will occur if there are an odd number of arguments,\nor any key name is NULL.\n \nExample\n \nSELECT JSON_OBJECT("id", 1, "name", "Monty");\n+---------------------------------------+\n| JSON_OBJECT("id", 1, "name", "Monty") |\n+---------------------------------------+\n| {"id": 1, "name": "Monty"} |\n+---------------------------------------+ +description=Returns a JSON object containing the given key/value pairs. The key/value list\ncan be empty.\n\nAn error will occur if there are an odd number of arguments, or any key name\nis NULL.\n\nExample\n-------\n\nSELECT JSON_OBJECT("id", 1, "name", "Monty");\n+---------------------------------------+\n| JSON_OBJECT("id", 1, "name", "Monty") |\n+---------------------------------------+\n| {"id": 1, "name": "Monty"} |\n+---------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_object/ +[JSON_OBJECTAGG] +declaration=key, value +category=JSON Functions +description=JSON_OBJECTAGG returns a JSON object containing key-value pairs. It takes two\nexpressions that evaluate to a single value, or two column names, as\narguments, the first used as a key, and the second as a value.\n\nThe maximum returned length in bytes is determined by the group_concat_max_len\nserver system variable.\n\nReturns NULL in the case of an error, or if the result contains no rows.\n\nJSON_OBJECTAGG cannot currently be used as a window function.\n\nExamples\n--------\n\nselect * from t1;\n+------+-------+\n| a | b |\n+------+-------+\n| 1 | Hello |\n| 1 | World |\n| 2 | This |\n+------+-------+\n\nSELECT JSON_OBJECTAGG(a, b) FROM t1;\n+----------------------------------------+\n| JSON_OBJECTAGG(a, b) |\n+----------------------------------------+\n| {"1":"Hello", "1":"World", "2":"This"} |\n+----------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_objectagg/ +[JSON_OBJECT_FILTER_KEYS] +declaration=obj, array_keys +category=JSON Functions +description=JSON_OBJECT_FILTER_KEYS returns a JSON object with keys from the object that\nare also present in the array as string. It is used when one wants to get\nkey-value pair such that the keys are common but the values may not be common.\n\nExample\n-------\n\nSET @obj1= '{ "a": 1, "b": 2, "c": 3}';\nSET @obj2= '{"b" : 10, "c": 20, "d": 30}';\nSELECT JSON_OBJECT_FILTER_KEYS (@obj1, JSON_ARRAY_INTERSECT(JSON_KEYS(@obj1),\nJSON_KEYS(@obj2)));\n+------------------------------------------------------------------------------\n------------+\n| JSON_OBJECT_FILTER_KEYS (@obj1, JSON_ARRAY_INTERSECT(JSON_KEYS(@obj1),\nJSON_KEYS(@obj2))) |\n+------------------------------------------------------------------------------\n------------+\n| {"b": 2, "c": 3} \n |\n+------------------------------------------------------------------------------\n------------+\n\nURL: https://mariadb.com/kb/en/json_object_filter_keys/ +[JSON_OBJECT_TO_ARRAY] +declaration=Obj +category=JSON Functions +description=It is used to convert all JSON objects found in a JSON document to JSON arrays\nwhere each item in the outer array represents a single key-value pair from the\nobject. It is used when we want not just common keys, but also common values.\nIt can be used in conjunction with JSON_ARRAY_INTERSECT().\n\nExamples\n--------\n\nSET @obj1= '{ "a": [1, 2, 3], "b": { "key1":"val1", "key2": {"key3":"val3"}\n}}';\n\nSELECT JSON_OBJECT_TO_ARRAY(@obj1);\n+-----------------------------------------------------------------------+\n| JSON_OBJECT_TO_ARRAY(@obj1) |\n+-----------------------------------------------------------------------+\n| [["a", [1, 2, 3]], ["b", {"key1": "val1", "key2": {"key3": "val3"}}]] |\n+-----------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_object_to_array/ +[JSON_OVERLAPS] +declaration=json_doc1, json_doc2 +category=JSON Functions +description=JSON_OVERLAPS() compares two json documents and returns true if they have at\nleast one common key-value pair between two objects, array element common\nbetween two arrays, or array element common with scalar if one of the\narguments is a scalar and other is an array. If two json documents are\nscalars, it returns true if they have same type and value.\n\nIf none of the above conditions are satisfied then it returns false.\n\nExamples\n--------\n\nSELECT JSON_OVERLAPS('false', 'false');\n+---------------------------------+\n| JSON_OVERLAPS('false', 'false') |\n+---------------------------------+\n| 1 |\n+---------------------------------+\n\nSELECT JSON_OVERLAPS('true', '["abc", 1, 2, true, false]');\n+----------------------------------------------------+\n| JSON_OVERLAPS('true','["abc", 1, 2, true, false]') |\n+----------------------------------------------------+\n| 1 |\n+----------------------------------------------------+\n\nSELECT JSON_OVERLAPS('{"A": 1, "B": {"C":2}}', '{"A": 2, "B": {"C":2}}') AS\nis_overlap;\n+---------------------+\n| is_overlap |\n+---------------------+\n| 1 |\n+---------------------+\n\nPartial match is considered as no-match.\n\nExamples\n--------\n\nSELECT JSON_OVERLAPS('[1, 2, true, false, null]', '[3, 4, [1]]') AS is_overlap;\n+--------------------- +\n| is_overlap |\n+----------------------+\n| 0 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/json_overlaps/ [JSON_QUERY] declaration=json_doc, path category=JSON Functions -description=Given a JSON document, returns an object or array specified\nby the path. Returns NULL if not given a valid JSON\ndocument, or if there is no match.\n \n\nselect json_query(''{"key1":{"a":1, "b":[1,2]}}'',\n''$.key1'');\n+-----------------------------------------------------+\n| json_query(''{"key1":{"a":1, "b":[1,2]}}'',\n''$.key1'') |\n+-----------------------------------------------------+\n| {"a":1, "b":[1,2]} |\n+-----------------------------------------------------+\n \nselect json_query(''{"key1":123, "key1": [1,2,3]}'',\n''$.key1'');\n+-------------------------------------------------------+\n| json_query(''{"key1":123, "key1": [1,2,3]}'',\n''$.key1'') |\n+-------------------------------------------------------+\n| [1,2,3] |\n+-------------------------------------------------------+ +description=Given a JSON document, returns an object or array specified by the path.\nReturns NULL if not given a valid JSON document, or if there is no match.\n\nExamples\n--------\n\nselect json_query('{"key1":{"a":1, "b":[1,2]}}', '$.key1');\n+-----------------------------------------------------+\n| json_query('{"key1":{"a":1, "b":[1,2]}}', '$.key1') |\n+-----------------------------------------------------+\n| {"a":1, "b":[1,2]} |\n+-----------------------------------------------------+\n\nselect json_query('{"key1":123, "key1": [1,2,3]}', '$.key1');\n+-------------------------------------------------------+\n| json_query('{"key1":123, "key1": [1,2,3]}', '$.key1') |\n+-------------------------------------------------------+\n| [1,2,3] |\n+-------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_query/ [JSON_QUOTE] declaration=json_value category=JSON Functions -description=Quotes a string as a JSON value, usually for producing valid\nJSON string literals for inclusion in JSON documents. Wraps\nthe string with double quote characters and escapes interior\nquotes and other special characters, returning a utf8mb4\nstring. \n \nReturns NULL if the argument is NULL.\n \n\nSELECT JSON_QUOTE(''A''), JSON_QUOTE("B"),\nJSON_QUOTE(''"C"'');\n+-----------------+-----------------+-------------------+\n| JSON_QUOTE(''A'') | JSON_QUOTE("B") |\nJSON_QUOTE(''"C"'') |\n+-----------------+-----------------+-------------------+\n| "A" | "B" | "\"C\"" |\n+-----------------+-----------------+-------------------+ +description=Quotes a string as a JSON value, usually for producing valid JSON string\nliterals for inclusion in JSON documents. Wraps the string with double quote\ncharacters and escapes interior quotes and other special characters, returning\na utf8mb4 string.\n\nReturns NULL if the argument is NULL.\n\nExamples\n--------\n\nSELECT JSON_QUOTE('A'), JSON_QUOTE("B"), JSON_QUOTE('"C"');\n+-----------------+-----------------+-------------------+\n| JSON_QUOTE('A') | JSON_QUOTE("B") | JSON_QUOTE('"C"') |\n+-----------------+-----------------+-------------------+\n| "A" | "B" | "\"C\"" |\n+-----------------+-----------------+-------------------+\n\nURL: https://mariadb.com/kb/en/json_quote/ [JSON_REMOVE] declaration=json_doc, path[, path] ... category=JSON Functions -description=Removes data from a JSON document returning the result, or\nNULL if any of the arguments are null. If the element does\nnot exist in the document, no changes are made.\n \nAn error will occur if JSON document is invalid, the path is\ninvalid or if the path contains a * or ** wildcard.\n \nPath arguments are evaluated from left to right, with the\nresult from the earlier evaluation being used as the value\nfor the next.\n \n\nSELECT JSON_REMOVE(''{"A": 1, "B": 2, "C": {"D":\n3}}'', ''$.C'');\n+-------------------------------------------------------+\n| JSON_REMOVE(''{"A": 1, "B": 2, "C": {"D": 3}}'',\n''$.C'') |\n+-------------------------------------------------------+\n| {"A": 1, "B": 2} |\n+-------------------------------------------------------+\n \nSELECT JSON_REMOVE(''["A", "B", ["C", "D"],\n"E"]'', ''$[1]'');\n+----------------------------------------------------+\n| JSON_REMOVE(''["A", "B", ["C", "D"], "E"]'',\n''$[1]'') |\n+----------------------------------------------------+\n| ["A", ["C", "D"], "E"] |\n+----------------------------------------------------+ +description=Removes data from a JSON document returning the result, or NULL if any of the\narguments are null. If the element does not exist in the document, no changes\nare made.\n\nThe function returns NULL and throws a warning if the JSON document is\ninvalid, the path is invalid, contains a range, or contains a * or ** wildcard.\n\nPath arguments are evaluated from left to right, with the result from the\nearlier evaluation being used as the value for the next.\n\nExamples\n--------\n\nSELECT JSON_REMOVE('{"A": 1, "B": 2, "C": {"D": 3}}', '$.C');\n+-------------------------------------------------------+\n| JSON_REMOVE('{"A": 1, "B": 2, "C": {"D": 3}}', '$.C') |\n+-------------------------------------------------------+\n| {"A": 1, "B": 2} |\n+-------------------------------------------------------+\n\nSELECT JSON_REMOVE('["A", "B", ["C", "D"], "E"]', '$[1]');\n+----------------------------------------------------+\n| JSON_REMOVE('["A", "B", ["C", "D"], "E"]', '$[1]') |\n+----------------------------------------------------+\n| ["A", ["C", "D"], "E"] |\n+----------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_remove/ [JSON_REPLACE] declaration=json_doc, path, val[, path, val] ... category=JSON Functions -description=Replaces existing values in a JSON document, returning the\nresult, or NULL if any of the arguments are NULL. \n \nAn error will occur if the JSON document is invalid, the\npath is invalid or if the path contains a * or ** wildcard.\n \nPaths and values are evaluated from left to right, with the\nresult from the earlier evaluation being used as the value\nfor the next.\n \nJSON_REPLACE can only update data, while JSON_INSERT can\nonly insert. JSON_SET can update or insert data. \n \n\nSELECT JSON_REPLACE(''{ "A": 1, "B": [2, 3]}'',\n''$.B[1]'', 4);\n+-----------------------------------------------------+\n| JSON_REPLACE(''{ "A": 1, "B": [2, 3]}'', ''$.B[1]'',\n4) |\n+-----------------------------------------------------+\n| { "A": 1, "B": [2, 4]} |\n+-----------------------------------------------------+ +description=Replaces existing values in a JSON document, returning the result, or NULL if\nany of the arguments are NULL.\n\nAn error will occur if the JSON document is invalid, the path is invalid or if\nthe path contains a * or ** wildcard.\n\nPaths and values are evaluated from left to right, with the result from the\nearlier evaluation being used as the value for the next.\n\nJSON_REPLACE can only update data, while JSON_INSERT can only insert. JSON_SET\ncan update or insert data.\n\nExamples\n--------\n\nSELECT JSON_REPLACE('{ "A": 1, "B": [2, 3]}', '$.B[1]', 4);\n+-----------------------------------------------------+\n| JSON_REPLACE('{ "A": 1, "B": [2, 3]}', '$.B[1]', 4) |\n+-----------------------------------------------------+\n| { "A": 1, "B": [2, 4]} |\n+-----------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_replace/ +[JSON_SCHEMA_VALID] +declaration=schema, json +category=JSON Functions +description=JSON_SCHEMA_VALID allows MariaDB to support JSON schema validation. If a given\njson is valid against a schema it returns true. When JSON does not validate\nagainst the schema, it does not return a message about which keyword it failed\nagainst and only returns false.\n\nThe function supports JSON Schema Draft 2020 with a few exceptions:\n\n* External resources are not supported\n* Hyper schema keywords are not supported\n* Formats like date, email etc are treated as annotations.\n\nExamples\n--------\n\nTo create validation rules for json field\n\nCREATE TABLE obj_table(val_obj JSON CHECK(JSON_SCHEMA_VALID('{\n "type":"object",\n "properties": {\n "number1":{\n "type":"number",\n "maximum":5,\n "const":4\n },\n "string1":{\n "type":"string",\n "maxLength":5,\n "minLength":3\n },\n "object1":{\n "type":"object",\n "properties":{\n "key1": {"type":"string"},\n "key2":{"type":"array"},\n "key3":{"type":"number", "minimum":3}\n },\n "dependentRequired": { "key1":["key3"] }\n }\n },\n "required":["number1","object1"]\n }', val_obj)));\n\nINSERT INTO obj_table VALUES(\n '{"number1":4, "string1":"abcd",\n "object1":{"key1":"val1", "key2":[1,2,3, "string1"], "key3":4}}'\n);\n\nINSERT INTO obj_table VALUES(\n '{"number1":3, "string1":"abcd",\n "object1":{"key1":"val1", "key2":[1,2,3, "string1"], "key3":4}}'\n ... [JSON_SEARCH] declaration=json_doc, return_arg, search_str[, escape_char[, path] ...] category=JSON Functions -description=Returns the path to the given string within a JSON document,\nor NULL if any of json_doc, search_str or a path argument is\nNULL; if the search string is not found, or if no path\nexists within the document. \n \nA warning will occur if the JSON document is not valid, any\nof the path arguments are not valid, if return_arg is\nneither one nor all, or if the escape character is not a\nconstant. NULL will be returned.\n \nreturn_arg can be one of two values:\n''one: Terminates after finding the first match, so will\nreturn one path string. If there is more than one match, it\nis undefined which is considered first.\nall: Returns all matching path strings, without duplicates.\nMultiple strings are autowrapped as an array. The order is\nundefined.\n \n\nSET @json = ''["A", [{"B": "1"}], {"C":"AB"},\n{"D":"BC"}]'';\n \nSELECT JSON_SEARCH(@json, ''one'', ''AB'');\n+---------------------------------+\n| JSON_SEARCH(@json, ''one'', ''AB'') |\n+---------------------------------+\n| "$[2].C" |\n+---------------------------------+ +description=Returns the path to the given string within a JSON document, or NULL if any of\njson_doc, search_str or a path argument is NULL; if the search string is not\nfound, or if no path exists within the document.\n\nA warning will occur if the JSON document is not valid, any of the path\narguments are not valid, if return_arg is neither one nor all, or if the\nescape character is not a constant. NULL will be returned.\n\nreturn_arg can be one of two values:\n\n* 'one: Terminates after finding the first match, so will return one path\nstring. If there is more than one match, it is undefined which is considered\nfirst.\n* all: Returns all matching path strings, without duplicates. Multiple strings\nare autowrapped as an array. The order is undefined.\n\nExamples\n--------\n\nSET @json = '["A", [{"B": "1"}], {"C":"AB"}, {"D":"BC"}]';\n\nSELECT JSON_SEARCH(@json, 'one', 'AB');\n+---------------------------------+\n| JSON_SEARCH(@json, 'one', 'AB') |\n+---------------------------------+\n| "$[2].C" |\n+---------------------------------+\n\nURL: https://mariadb.com/kb/en/json_search/ [JSON_SET] declaration=json_doc, path, val[, path, val] ... category=JSON Functions -description=Updates or inserts data into a JSON document, returning the\nresult, or NULL if any of the arguments are NULL or the\noptional path fails to find an object.\n \nAn error will occur if the JSON document is invalid, the\npath is invalid or if the path contains a * or wildcard.\n \nJSON_SET can update or insert data, while JSON_REPLACE can\nonly update, and JSON_INSERT only insert. +description=Updates or inserts data into a JSON document, returning the result, or NULL if\nany of the arguments are NULL or the optional path fails to find an object.\n\nAn error will occur if the JSON document is invalid, the path is invalid or if\nthe path contains a * or wildcard.\n\nJSON_SET can update or insert data, while JSON_REPLACE can only update, and\nJSON_INSERT only insert.\n\nExamples\n--------\n\nSELECT JSON_SET(Priv, '$.locked', 'true') FROM mysql.global_priv\n\nURL: https://mariadb.com/kb/en/json_set/ +[JSON_TABLE] +declaration=json_doc, context_path COLUMNS (column_list +category=JSON Functions +description=JSON_TABLE can be used in contexts where a table reference can be used; in the\nFROM clause of a SELECT statement, and in multi-table UPDATE/DELETE statements.\n\njson_doc is the JSON document to extract data from. In the simplest case, it\nis a string literal containing JSON. In more complex cases it can be an\narbitrary expression returning JSON. The expression may have references to\ncolumns of other tables. However, one can only refer to tables that precede\nthis JSON_TABLE invocation. For RIGHT JOIN, it is assumed that its outer side\nprecedes the inner. All tables in outer selects are also considered preceding.\n\ncontext_path is a JSON Path expression pointing to a collection of nodes in\njson_doc that will be used as the source of rows.\n\nThe COLUMNS clause declares the names and types of the columns that JSON_TABLE\nreturns, as well as how the values of the columns are produced.\n\nColumn Definitions\n------------------\n\nThe following types of columns are supported:\n\nPath Columns\n------------\n\nname type PATH path_str [on_empty] [on_error]\n\nLocates the JSON node pointed to by path_str and returns its value. The\npath_str is evaluated using the current row source node as the context node.\n\nset @json='\n[\n {"name":"Laptop", "color":"black", "price":"1000"},\n {"name":"Jeans", "color":"blue"}\n]';\n\nselect * from json_table(@json, '$[*]' \n columns(\n name varchar(10) path '$.name',\n color varchar(10) path '$.color',\n price decimal(8,2) path '$.price' )\n) as jt;\n+--------+-------+---------+\n| name | color | price |\n+--------+-------+---------+\n| Laptop | black | 1000.00 |\n| Jeans | blue | NULL |\n+--------+-------+---------+\n\nThe on_empty and on_error clauses specify the actions to be performed when the\nvalue was not found or there was an error condition. See the ON EMPTY and ON\n ... [JSON_TYPE] declaration=json_val category=JSON Functions -description=Returns the type of a JSON value, or NULL if the argument is\nnull.\n \nAn error will occur if the argument is an invalid JSON\nvalue.\n \nThe following is a complete list of the possible return\ntypes:\n \nReturn type | Value | \n \nARRAY | JSON array | \n \nBIT | MariaDB BIT scalar | \n \nBLOB | MariaDB binary types (BINARY, VARBINARY or BLOB) | \n \nBOOLEAN | JSON true/false literals | \n \nDATE | MariaDB DATE scalar | \n \nDATETIME | MariaDB DATETIME or TIMESTAMP scalar | \n \nDECIMAL | MariaDB DECIMAL or NUMERIC scalar | \n \nDOUBLE | MariaDB DOUBLE FLOAT scalar | \n \nINTEGER | MariaDB integer types (TINYINT, SMALLINT,\nMEDIUMINT, INT or BIGINT) | \n \nNULL | JSON null literal or NULL argument | \n \nOBJECT | JSON object | \n \nOPAQUE | Any valid JSON value that is not one of the other\ntypes. | \n \nSTRING | MariaDB character types (CHAR, VARCHAR, TEXT, ENUM\nor SET) | \n \nTIME | MariaDB TIME scalar | \n \n\nSELECT JSON_TYPE(''{"A": 1, "B": 2, "C": 3}'');\n+---------------------------------------+\n| JSON_TYPE(''{"A": 1, "B": 2, "C": 3}'') |\n+---------------------------------------+\n| OBJECT |\n+---------------------------------------+ +description=Returns the type of a JSON value (as a string), or NULL if the argument is\nnull.\n\nAn error will occur if the argument is an invalid JSON value.\n\nThe following is a complete list of the possible return types:\n\n+-----------------------------------+-----------------+-----------------------+\n| Return type | Value | Example |\n+-----------------------------------+-----------------+-----------------------+\n| ARRAY | JSON array | [1, 2, {"key": |\n| | | "value"}] |\n+-----------------------------------+-----------------+-----------------------+\n| OBJECT | JSON object | {"key":"value"} |\n+-----------------------------------+-----------------+-----------------------+\n| BOOLEAN | JSON | true, false |\n| | true/false | |\n| | literals | |\n+-----------------------------------+-----------------+-----------------------+\n| DOUBLE | A number with | 1.2 |\n| | at least one | |\n| | floating point | |\n| | decimal. | |\n+-----------------------------------+-----------------+-----------------------+\n| INTEGER | A number | 1 |\n| | without a | |\n| | floating point | |\n| | decimal. | |\n+-----------------------------------+-----------------+-----------------------+\n| NULL | JSON null | null |\n| | literal (this | |\n| | is returned as | |\n| | a string, not | |\n| | to be confused | |\n| | with the SQL | |\n| | NULL value!) | |\n+-----------------------------------+-----------------+-----------------------+\n| STRING | JSON String | "a sample string" |\n+-----------------------------------+-----------------+-----------------------+\n\nExamples\n--------\n\nSELECT JSON_TYPE('{"A": 1, "B": 2, "C": 3}');\n+---------------------------------------+\n| JSON_TYPE('{"A": 1, "B": 2, "C": 3}') |\n+---------------------------------------+\n| OBJECT |\n+---------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_type/ [JSON_UNQUOTE] declaration=val category=JSON Functions -description=Unquotes a JSON value, returning a string, or NULL if the\nargument is null. \n \nAn error will occur if the given value begins and ends with\ndouble quotes and is an invalid JSON string literal.\n \nCertain character sequences have special meanings within a\nstring. Usually, a backspace is ignored, but the escape\nsequences in the table below are recognised by MariaDB,\nunless the SQL Mode is set to NO_BACKSLASH_ESCAPES SQL.\n \nEscape sequence | Character | \n \n\" | Double quote (") | \n \n\b | Backspace | \n \n\f | Formfeed | \n \n\n | Newline (linefeed) | \n \n\r | Carriage return | \n \n\t | Tab | \n \n\\ | Backslash (\) | \n \n\uXXXX | UTF-8 bytes for Unicode value XXXX | \n \n\nSELECT JSON_UNQUOTE(''"Monty"'');\n+-------------------------+\n| JSON_UNQUOTE(''"Monty"'') |\n+-------------------------+\n| Monty |\n+-------------------------+\n \nWith the default SQL Mode:\n \nSELECT JSON_UNQUOTE(''Si\bng\ting'');\n+-----------------------------+\n| JSON_UNQUOTE(''Si\bng\ting'') |\n+-----------------------------+\n| Sng ing |\n+-----------------------------+\n \nSetting NO_BACKSLASH_ESCAPES:\n \nSET @@sql_mode = ''NO_BACKSLASH_ESCAPES'';\n \nSELECT JSON_UNQUOTE(''Si\bng\ting'');\n+-----------------------------+\n| JSON_UNQUOTE(''Si\bng\ting'') |\n+-----------------------------+\n| Si\bng\ting |\n+-----------------------------+ +description=Unquotes a JSON value, returning a string, or NULL if the argument is null.\n\nAn error will occur if the given value begins and ends with double quotes and\nis an invalid JSON string literal.\n\nIf the given value is not a JSON string, value is passed through unmodified.\n\nCertain character sequences have special meanings within a string. Usually, a\nbackslash is ignored, but the escape sequences in the table below are\nrecognised by MariaDB, unless the SQL Mode is set to NO_BACKSLASH_ESCAPES SQL.\n\n+-----------------------------------------------+-----------------------------+\n| Escape sequence | Character |\n+-----------------------------------------------+-----------------------------+\n| \" | Double quote (") |\n+-----------------------------------------------+-----------------------------+\n| \b | Backslash |\n+-----------------------------------------------+-----------------------------+\n| \f | Formfeed |\n+-----------------------------------------------+-----------------------------+\n| \n | Newline (linefeed) |\n+-----------------------------------------------+-----------------------------+\n| \r | Carriage return |\n+-----------------------------------------------+-----------------------------+\n| \t | Tab |\n+-----------------------------------------------+-----------------------------+\n| \\ | Backslash (\) |\n+-----------------------------------------------+-----------------------------+\n| \uXXXX | UTF-8 bytes for Unicode |\n| | value XXXX |\n+-----------------------------------------------+-----------------------------+\n\nExamples\n--------\n\nSELECT JSON_UNQUOTE('"Monty"');\n+-------------------------+\n| JSON_UNQUOTE('"Monty"') |\n+-------------------------+\n| Monty |\n+-------------------------+\n\nWith the default SQL Mode:\n\nSELECT JSON_UNQUOTE('Si\bng\ting');\n+-----------------------------+\n| JSON_UNQUOTE('Si\bng\ting') |\n+-----------------------------+\n| Sng ing |\n+-----------------------------+\n ... [JSON_VALID] declaration=value category=JSON Functions -description=Indicates whether the given value is a valid JSON document\nor not. Returns 1 if valid, 0 if not, and NULL if the\nargument is NULL.\n \nFrom MariaDB 10.4.3, the JSON_VALID function is\nautomatically used as a CHECK constraint for the JSON data\ntype alias in order to ensure that a valid json document is\ninserted. \n \n\nSELECT JSON_VALID(''{"id": 1, "name": "Monty"}'');\n+------------------------------------------+\n| JSON_VALID(''{"id": 1, "name": "Monty"}'') |\n+------------------------------------------+\n| 1 |\n+------------------------------------------+\n \nSELECT JSON_VALID(''{"id": 1, "name": "Monty",\n"oddfield"}'');\n+------------------------------------------------------+\n| JSON_VALID(''{"id": 1, "name": "Monty",\n"oddfield"}'') |\n+------------------------------------------------------+\n| 0 |\n+------------------------------------------------------+ +description=Indicates whether the given value is a valid JSON document or not. Returns 1\nif valid, 0 if not, and NULL if the argument is NULL.\n\nFrom MariaDB 10.4.3, the JSON_VALID function is automatically used as a CHECK\nconstraint for the JSON data type alias in order to ensure that a valid json\ndocument is inserted.\n\nExamples\n--------\n\nSELECT JSON_VALID('{"id": 1, "name": "Monty"}');\n+------------------------------------------+\n| JSON_VALID('{"id": 1, "name": "Monty"}') |\n+------------------------------------------+\n| 1 |\n+------------------------------------------+\n\nSELECT JSON_VALID('{"id": 1, "name": "Monty", "oddfield"}');\n+------------------------------------------------------+\n| JSON_VALID('{"id": 1, "name": "Monty", "oddfield"}') |\n+------------------------------------------------------+\n| 0 |\n+------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/json_valid/ [JSON_VALUE] declaration=json_doc, path category=JSON Functions -description=Given a JSON document, returns the scalar specified by the\npath. Returns NULL if not given a valid JSON document, or if\nthere is no match.\n \n\nselect json_value(''{"key1":123}'', ''$.key1'');\n+--------------------------------------+\n| json_value(''{"key1":123}'', ''$.key1'') |\n+--------------------------------------+\n| 123 |\n+--------------------------------------+\n \nselect json_value(''{"key1": [1,2,3], "key1":123}'',\n''$.key1'');\n+-------------------------------------------------------+\n| json_value(''{"key1": [1,2,3], "key1":123}'',\n''$.key1'') |\n+-------------------------------------------------------+\n| 123 |\n+-------------------------------------------------------+ +description=Given a JSON document, returns the scalar specified by the path. Returns NULL\nif not given a valid JSON document, or if there is no match.\n\nExamples\n--------\n\nselect json_value('{"key1":123}', '$.key1');\n+--------------------------------------+\n| json_value('{"key1":123}', '$.key1') |\n+--------------------------------------+\n| 123 |\n+--------------------------------------+\n\nselect json_value('{"key1": [1,2,3], "key1":123}', '$.key1');\n+-------------------------------------------------------+\n| json_value('{"key1": [1,2,3], "key1":123}', '$.key1') |\n+-------------------------------------------------------+\n| 123 |\n+-------------------------------------------------------+\n\nIn the SET statement below, two escape characters are needed, as a single\nescape character would be applied by the SQL parser in the SET statement, and\nthe escaped character would not form part of the saved value.\n\nSET @json = '{"key1":"60\\" Table", "key2":"1"}';\n\nSELECT JSON_VALUE(@json,'$.key1') AS Name , json_value(@json,'$.key2') as ID;\n+-----------+------+\n| Name | ID |\n+-----------+------+\n| 60" Table | 1 |\n+-----------+------+\n\nURL: https://mariadb.com/kb/en/json_value/ +[KDF] +declaration= +category=Encryption Functions +description=KDF is a key derivation function, similar to OpenSSL's EVP_KDF_derive(). The\npurpose of a KDF is to be slow, so if the calculated value is lost/stolen, the\noriginal key_str is not achievable easily with modern GPU. KDFs are therefore\nan ideal replacement for password hashes. KDFs can also pad out a password\nsecret to the number of bits used in encryption algorithms.\n\nFor generating good encryption keys for AES_ENCRYPT a less expensive function,\nbut cryptographically secure function like RANDOM_BYTES is recommended..\n\n* kdf_name is "hkdf" or "pbkdf2_hmac" (default)\n* width (in bits) can be any number divisible by 8, by default it's taken from\n@@block_encryption_mode\n* iterations must be positive, and is 1000 by default\n\nNote that OpenSSL 1.0 doesn't support HKDF, so in this case NULL is returned.\nThis OpenSSL version is still used in SLES 12 and CentOS 7.\n\nExamples\n--------\n\nselect hex(kdf('foo', 'bar', 'infa', 'hkdf')); \n+----------------------------------------+\n| hex(kdf('foo', 'bar', 'infa', 'hkdf')) |\n+----------------------------------------+\n| 612875F859CFB4EE0DFEFF9F2A18E836 |\n+----------------------------------------+\n\nURL: https://mariadb.com/kb/en/kdf/ +[LAG] +declaration=expr[, offset] +category=Window Functions +description=The LAG function accesses data from a previous row according to the ORDER BY\nclause without the need for a self-join. The specific row is determined by the\noffset (default 1), which specifies the number of rows behind the current row\nto use. An offset of 0 is the current row.\n\nExamples\n--------\n\nCREATE TABLE t1 (pk int primary key, a int, b int, c char(10), d decimal(10,\n3), e real);\n\nINSERT INTO t1 VALUES\n ( 1, 0, 1, 'one', 0.1, 0.001),\n ( 2, 0, 2, 'two', 0.2, 0.002),\n ( 3, 0, 3, 'three', 0.3, 0.003),\n ( 4, 1, 2, 'three', 0.4, 0.004),\n ( 5, 1, 1, 'two', 0.5, 0.005),\n ( 6, 1, 1, 'one', 0.6, 0.006),\n ( 7, 2, NULL, 'n_one', 0.5, 0.007),\n ( 8, 2, 1, 'n_two', NULL, 0.008),\n ( 9, 2, 2, NULL, 0.7, 0.009),\n (10, 2, 0, 'n_four', 0.8, 0.010),\n (11, 2, 10, NULL, 0.9, NULL);\n\nSELECT pk, LAG(pk) OVER (ORDER BY pk) AS l,\n LAG(pk,1) OVER (ORDER BY pk) AS l1,\n LAG(pk,2) OVER (ORDER BY pk) AS l2,\n LAG(pk,0) OVER (ORDER BY pk) AS l0,\n LAG(pk,-1) OVER (ORDER BY pk) AS lm1,\n LAG(pk,-2) OVER (ORDER BY pk) AS lm2\nFROM t1;\n+----+------+------+------+------+------+------+\n| pk | l | l1 | l2 | l0 | lm1 | lm2 |\n+----+------+------+------+------+------+------+\n| 1 | NULL | NULL | NULL | 1 | 2 | 3 |\n| 2 | 1 | 1 | NULL | 2 | 3 | 4 |\n| 3 | 2 | 2 | 1 | 3 | 4 | 5 |\n| 4 | 3 | 3 | 2 | 4 | 5 | 6 |\n| 5 | 4 | 4 | 3 | 5 | 6 | 7 |\n| 6 | 5 | 5 | 4 | 6 | 7 | 8 |\n| 7 | 6 | 6 | 5 | 7 | 8 | 9 |\n| 8 | 7 | 7 | 6 | 8 | 9 | 10 |\n| 9 | 8 | 8 | 7 | 9 | 10 | 11 |\n| 10 | 9 | 9 | 8 | 10 | 11 | NULL |\n| 11 | 10 | 10 | 9 | 11 | NULL | NULL |\n+----+------+------+------+------+------+------+\n\nURL: https://mariadb.com/kb/en/lag/ [LAST_DAY] declaration=date category=Date and Time Functions -description=Takes a date or datetime value and returns the corresponding\nvalue for\nthe last day of the month. Returns NULL if the argument is\ninvalid.\n \n\nSELECT LAST_DAY(''2003-02-05'');\n+------------------------+\n| LAST_DAY(''2003-02-05'') |\n+------------------------+\n| 2003-02-28 |\n+------------------------+\n \nSELECT LAST_DAY(''2004-02-05'');\n+------------------------+\n| LAST_DAY(''2004-02-05'') |\n+------------------------+\n| 2004-02-29 |\n+------------------------+\n \nSELECT LAST_DAY(''2004-01-01 01:01:01'');\n+---------------------------------+\n| LAST_DAY(''2004-01-01 01:01:01'') |\n+---------------------------------+\n| 2004-01-31 |\n+---------------------------------+\n \nSELECT LAST_DAY(''2003-03-32'');\n+------------------------+\n| LAST_DAY(''2003-03-32'') |\n+------------------------+\n| NULL |\n+------------------------+\n1 row in set, 1 warning (0.00 sec)\n \nWarning (Code 1292): Incorrect datetime value:\n''2003-03-32'' -[LAST_INSERT_ID1] -name=LAST_INSERT_ID +description=Takes a date or datetime value and returns the corresponding value for the\nlast day of the month. Returns NULL if the argument is invalid.\n\nExamples\n--------\n\nSELECT LAST_DAY('2003-02-05');\n+------------------------+\n| LAST_DAY('2003-02-05') |\n+------------------------+\n| 2003-02-28 |\n+------------------------+\n\nSELECT LAST_DAY('2004-02-05');\n+------------------------+\n| LAST_DAY('2004-02-05') |\n+------------------------+\n| 2004-02-29 |\n+------------------------+\n\nSELECT LAST_DAY('2004-01-01 01:01:01');\n+---------------------------------+\n| LAST_DAY('2004-01-01 01:01:01') |\n+---------------------------------+\n| 2004-01-31 |\n+---------------------------------+\n\nSELECT LAST_DAY('2003-03-32');\n+------------------------+\n| LAST_DAY('2003-03-32') |\n+------------------------+\n| NULL |\n+------------------------+\n1 row in set, 1 warning (0.00 sec)\n\nWarning (Code 1292): Incorrect datetime value: '2003-03-32'\n\nURL: https://mariadb.com/kb/en/last_day/ +[LAST_INSERT_ID] declaration= category=Information Functions -description=LAST_INSERT_ID() (no arguments) returns\nthe first automatically generated value successfully\ninserted for an\nAUTO_INCREMENT column as a result of the most recently\nexecuted INSERT\nstatement. The value of LAST_INSERT_ID() remains unchanged\nif no rows\nare successfully inserted.\n \nIf one gives an argument to LAST_INSERT_ID(), then it will\nreturn the value of the expression and\nthe next call to LAST_INSERT_ID() will return the same\nvalue. The value will also be sent to the client\nand can be accessed by the mysql_insert_id function.\n \nFor example, after inserting a row that generates an\nAUTO_INCREMENT\nvalue, you can get the value like this:\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 9 |\n+------------------+\n \nYou can also use LAST_INSERT_ID() to delete the last\ninserted row:\n \nDELETE FROM product WHERE id = LAST_INSERT_ID();\n \nIf no rows were successfully inserted, LAST_INSERT_ID()\nreturns 0.\n \nThe value of LAST_INSERT_ID() will be consistent across all\nversions\nif all rows in the INSERT or UPDATE statement were\nsuccessful.\n \nThe currently executing statement does not affect the value\nof\nLAST_INSERT_ID(). Suppose that you generate an\nAUTO_INCREMENT value\nwith one statement, and then refer to LAST_INSERT_ID() in a\nmultiple-row INSERT statement that inserts rows into a table\nwith its\nown AUTO_INCREMENT column. The value of LAST_INSERT_ID()\nwill remain\nstable in the second statement; its value for the second and\nlater\nrows is not affected by the earlier row insertions.\n(However, if you\nmix references to LAST_INSERT_ID() and LAST_INSERT_ID(expr),\nthe\neffect is undefined.)\n \nIf the previous statement returned an error, the value of\nLAST_INSERT_ID() is undefined. For transactional tables, if\nthe\nstatement is rolled back due to an error, the value of\nLAST_INSERT_ID() is left undefined. For manual ROLLBACK, the\nvalue of\nLAST_INSERT_ID() is not restored to that before the\ntransaction; it\nremains as it was at the point of the ROLLBACK.\n \nWithin the body of a stored routine (procedure or function)\nor a\ntrigger, the value of LAST_INSERT_ID() changes the same way\nas for\nstatements executed outside the body of these kinds of\nobjects. The\neffect of a stored routine or trigger upon the value of\nLAST_INSERT_ID() that is seen by following statements\ndepends on the\nkind of routine:\nIf a stored procedure executes statements that change the\nvalue of LAST_INSERT_ID(), the new value will be seen by\nstatements that follow the procedure call.\n \nFor stored functions and triggers that change the value, the\nvalue is restored when the function or trigger ends, so\nfollowing statements will not see a changed value.\n \n\nCREATE TABLE t (\n id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, \n f VARCHAR(1)) \nENGINE = InnoDB;\n \nINSERT INTO t(f) VALUES(''a'');\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 1 |\n+------------------+\n \nINSERT INTO t(f) VALUES(''b'');\n \nINSERT INTO t(f) VALUES(''c'');\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 3 |\n+------------------+\n \nINSERT INTO t(f) VALUES(''d''),(''e'');\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 4 |\n+------------------+\n \nSELECT * FROM t;\n \n+----+------+\n| id | f |\n+----+------+\n| 1 | a |\n| 2 | b |\n| 3 | c |\n| 4 | d |\n| 5 | e |\n+----+------+\n \nSELECT LAST_INSERT_ID(12);\n+--------------------+\n| LAST_INSERT_ID(12) |\n+--------------------+\n| 12 |\n+--------------------+\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 12 |\n+------------------+\n \nINSERT INTO t(f) VALUES(''f'');\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 6 |\n+------------------+\n \nSELECT * FROM t;\n \n+----+------+\n| id | f |\n+----+------+\n| 1 | a |\n| 2 | b |\n| 3 | c |\n| 4 | d |\n| 5 | e |\n| 6 | f |\n+----+------+\n \nSELECT LAST_INSERT_ID(12);\n+--------------------+\n| LAST_INSERT_ID(12) |\n+--------------------+\n| 12 |\n+--------------------+\n \nINSERT INTO t(f) VALUES(''g'');\n \nSELECT * FROM t;\n \n+----+------+\n| id | f |\n+----+------+\n| 1 | a |\n| 2 | b |\n| 3 | c |\n| 4 | d |\n| 5 | e |\n| 6 | f |\n| 7 | g |\n+----+------+ -[LAST_INSERT_ID2] -name=LAST_INSERT_ID -declaration=expr -category=Information Functions -description=LAST_INSERT_ID() (no arguments) returns\nthe first automatically generated value successfully\ninserted for an\nAUTO_INCREMENT column as a result of the most recently\nexecuted INSERT\nstatement. The value of LAST_INSERT_ID() remains unchanged\nif no rows\nare successfully inserted.\n \nIf one gives an argument to LAST_INSERT_ID(), then it will\nreturn the value of the expression and\nthe next call to LAST_INSERT_ID() will return the same\nvalue. The value will also be sent to the client\nand can be accessed by the mysql_insert_id function.\n \nFor example, after inserting a row that generates an\nAUTO_INCREMENT\nvalue, you can get the value like this:\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 9 |\n+------------------+\n \nYou can also use LAST_INSERT_ID() to delete the last\ninserted row:\n \nDELETE FROM product WHERE id = LAST_INSERT_ID();\n \nIf no rows were successfully inserted, LAST_INSERT_ID()\nreturns 0.\n \nThe value of LAST_INSERT_ID() will be consistent across all\nversions\nif all rows in the INSERT or UPDATE statement were\nsuccessful.\n \nThe currently executing statement does not affect the value\nof\nLAST_INSERT_ID(). Suppose that you generate an\nAUTO_INCREMENT value\nwith one statement, and then refer to LAST_INSERT_ID() in a\nmultiple-row INSERT statement that inserts rows into a table\nwith its\nown AUTO_INCREMENT column. The value of LAST_INSERT_ID()\nwill remain\nstable in the second statement; its value for the second and\nlater\nrows is not affected by the earlier row insertions.\n(However, if you\nmix references to LAST_INSERT_ID() and LAST_INSERT_ID(expr),\nthe\neffect is undefined.)\n \nIf the previous statement returned an error, the value of\nLAST_INSERT_ID() is undefined. For transactional tables, if\nthe\nstatement is rolled back due to an error, the value of\nLAST_INSERT_ID() is left undefined. For manual ROLLBACK, the\nvalue of\nLAST_INSERT_ID() is not restored to that before the\ntransaction; it\nremains as it was at the point of the ROLLBACK.\n \nWithin the body of a stored routine (procedure or function)\nor a\ntrigger, the value of LAST_INSERT_ID() changes the same way\nas for\nstatements executed outside the body of these kinds of\nobjects. The\neffect of a stored routine or trigger upon the value of\nLAST_INSERT_ID() that is seen by following statements\ndepends on the\nkind of routine:\nIf a stored procedure executes statements that change the\nvalue of LAST_INSERT_ID(), the new value will be seen by\nstatements that follow the procedure call.\n \nFor stored functions and triggers that change the value, the\nvalue is restored when the function or trigger ends, so\nfollowing statements will not see a changed value.\n \n\nCREATE TABLE t (\n id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, \n f VARCHAR(1)) \nENGINE = InnoDB;\n \nINSERT INTO t(f) VALUES(''a'');\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 1 |\n+------------------+\n \nINSERT INTO t(f) VALUES(''b'');\n \nINSERT INTO t(f) VALUES(''c'');\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 3 |\n+------------------+\n \nINSERT INTO t(f) VALUES(''d''),(''e'');\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 4 |\n+------------------+\n \nSELECT * FROM t;\n \n+----+------+\n| id | f |\n+----+------+\n| 1 | a |\n| 2 | b |\n| 3 | c |\n| 4 | d |\n| 5 | e |\n+----+------+\n \nSELECT LAST_INSERT_ID(12);\n+--------------------+\n| LAST_INSERT_ID(12) |\n+--------------------+\n| 12 |\n+--------------------+\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 12 |\n+------------------+\n \nINSERT INTO t(f) VALUES(''f'');\n \nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 6 |\n+------------------+\n \nSELECT * FROM t;\n \n+----+------+\n| id | f |\n+----+------+\n| 1 | a |\n| 2 | b |\n| 3 | c |\n| 4 | d |\n| 5 | e |\n| 6 | f |\n+----+------+\n \nSELECT LAST_INSERT_ID(12);\n+--------------------+\n| LAST_INSERT_ID(12) |\n+--------------------+\n| 12 |\n+--------------------+\n \nINSERT INTO t(f) VALUES(''g'');\n \nSELECT * FROM t;\n \n+----+------+\n| id | f |\n+----+------+\n| 1 | a |\n| 2 | b |\n| 3 | c |\n| 4 | d |\n| 5 | e |\n| 6 | f |\n| 7 | g |\n+----+------+ -[LAST_VALUE1] -name=LAST_VALUE +description=LAST_INSERT_ID() (no arguments) returns the first automatically generated\nvalue successfully inserted for an AUTO_INCREMENT column as a result of the\nmost recently executed INSERT statement. The value of LAST_INSERT_ID() remains\nunchanged if no rows are successfully inserted.\n\nIf one gives an argument to LAST_INSERT_ID(), then it will return the value of\nthe expression and the next call to LAST_INSERT_ID() will return the same\nvalue. The value will also be sent to the client and can be accessed by the\nmysql_insert_id function.\n\nFor example, after inserting a row that generates an AUTO_INCREMENT value, you\ncan get the value like this:\n\nSELECT LAST_INSERT_ID();\n+------------------+\n| LAST_INSERT_ID() |\n+------------------+\n| 9 |\n+------------------+\n\nYou can also use LAST_INSERT_ID() to delete the last inserted row:\n\nDELETE FROM product WHERE id = LAST_INSERT_ID();\n\nIf no rows were successfully inserted, LAST_INSERT_ID() returns 0.\n\nThe value of LAST_INSERT_ID() will be consistent across all versions if all\nrows in the INSERT or UPDATE statement were successful.\n\nThe currently executing statement does not affect the value of\nLAST_INSERT_ID(). Suppose that you generate an AUTO_INCREMENT value with one\nstatement, and then refer to LAST_INSERT_ID() in a multiple-row INSERT\nstatement that inserts rows into a table with its own AUTO_INCREMENT column.\nThe value of LAST_INSERT_ID() will remain stable in the second statement; its\nvalue for the second and later rows is not affected by the earlier row\ninsertions. (However, if you mix references to LAST_INSERT_ID() and\nLAST_INSERT_ID(expr), the effect is undefined.)\n\nIf the previous statement returned an error, the value of LAST_INSERT_ID() is\nundefined. For transactional tables, if the statement is rolled back due to an\nerror, the value of LAST_INSERT_ID() is left undefined. For manual ROLLBACK,\nthe value of LAST_INSERT_ID() is not restored to that before the transaction;\nit remains as it was at the point of the ROLLBACK.\n\nWithin the body of a stored routine (procedure or function) or a trigger, the\nvalue of LAST_INSERT_ID() changes the same way as for statements executed\noutside the body of these kinds of objects. The effect of a stored routine or\ntrigger upon the value of LAST_INSERT_ID() that is seen by following\nstatements depends on the kind of routine:\n\n ... +[LAST_VALUE] declaration=expr,[expr,...] category=Information Functions -description=LAST_VALUE() evaluates all expressions and returns the last.\n \nThis is useful together with setting user variables to a\nvalue with @var:=expr, for example when you want to get data\nof rows updated/deleted without having to do two queries\nagainst the table.\n \nSince MariaDB 10.2.2, LAST_VALUE can be used as a window\nfunction.\n \nReturns NULL if no last value exists.\n \n\nCREATE TABLE t1 (a int, b int);\nINSERT INTO t1 VALUES(1,10),(2,20);\nDELETE FROM t1 WHERE a=1 AND last_value(@a:=a,@b:=b,1);\nSELECT @a,@b;\n \n+------+------+\n| @a | @b |\n+------+------+\n| 1 | 10 |\n+------+------+\n \nAs a window function:\n \nCREATE TABLE t1 (\n pk int primary key,\n a int,\n b int,\n c char(10),\n d decimal(10, 3),\n e real\n);\n \nINSERT INTO t1 VALUES\n( 1, 0, 1, ''one'', 0.1, 0.001),\n( 2, 0, 2, ''two'', 0.2, 0.002),\n( 3, 0, 3, ''three'', 0.3, 0.003),\n( 4, 1, 2, ''three'', 0.4, 0.004),\n( 5, 1, 1, ''two'', 0.5, 0.005),\n( 6, 1, 1, ''one'', 0.6, 0.006),\n( 7, 2, NULL, ''n_one'', 0.5, 0.007),\n( 8, 2, 1, ''n_two'', NULL, 0.008),\n( 9, 2, 2, NULL, 0.7, 0.009),\n(10, 2, 0, ''n_four'', 0.8, 0.010),\n(11, 2, 10, NULL, 0.9, NULL);\n \nSELECT pk, FIRST_VALUE(pk) OVER (ORDER BY pk) AS first_asc,\n LAST_VALUE(pk) OVER (ORDER BY pk) AS last_asc,\n FIRST_VALUE(pk) OVER (ORDER BY pk DESC) AS first_desc,\n LAST_VALUE(pk) OVER (ORDER BY pk DESC) AS last_desc\nFROM t1\nORDER BY pk DESC;\n \n+----+-----------+----------+------------+-----------+\n| pk | first_asc | last_asc | first_desc | last_desc |\n+----+-----------+----------+------------+-----------+\n| 11 | 1 | 11 | 11 | 11 |\n| 10 | 1 | 10 | 11 | 10 |\n| 9 | 1 | 9 | 11 | 9 |\n| 8 | 1 | 8 | 11 | 8 |\n| 7 | 1 | 7 | 11 | 7 |\n| 6 | 1 | 6 | 11 | 6 |\n| 5 | 1 | 5 | 11 | 5 |\n| 4 | 1 | 4 | 11 | 4 |\n| 3 | 1 | 3 | 11 | 3 |\n| 2 | 1 | 2 | 11 | 2 |\n| 1 | 1 | 1 | 11 | 1 |\n+----+-----------+----------+------------+-----------+\n \nCREATE OR REPLACE TABLE t1 (i int);\nINSERT INTO t1 VALUES\n(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);\n \nSELECT i,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW\nand 1 FOLLOWING) AS f_1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW and\n1 FOLLOWING) AS l_1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING\nAND 1 FOLLOWING) AS f_1p1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND\n1 FOLLOWING) AS f_1p1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING\nAND 1 PRECEDING) AS f_2p1p,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING AND\n1 PRECEDING) AS f_2p1p,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING\nAND 2 FOLLOWING) AS f_1f2f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING AND\n2 FOLLOWING) AS f_1f2f\nFROM t1;\n \n+------+------+------+--------+--------+--------+--------+--------+--------+\n| i | f_1f | l_1f | f_1p1f | f_1p1f | f_2p1p | f_2p1p |\nf_1f2f | f_1f2f |\n+------+------+------+--------+--------+--------+--------+--------+--------+\n| 1 | 1 | 2 | 1 | 2 | NULL | NULL | 2 | 3 |\n| 2 | 2 | 3 | 1 | 3 | 1 | 1 | 3 | 4 |\n| 3 | 3 | 4 | 2 | 4 | 1 | 2 | 4 | 5 |\n| 4 | 4 | 5 | 3 | 5 | 2 | 3 | 5 | 6 |\n| 5 | 5 | 6 | 4 | 6 | 3 | 4 | 6 | 7 |\n| 6 | 6 | 7 | 5 | 7 | 4 | 5 | 7 | 8 |\n| 7 | 7 | 8 | 6 | 8 | 5 | 6 | 8 | 9 |\n| 8 | 8 | 9 | 7 | 9 | 6 | 7 | 9 | 10 |\n| 9 | 9 | 10 | 8 | 10 | 7 | 8 | 10 | 10 |\n| 10 | 10 | 10 | 9 | 10 | 8 | 9 | NULL | NULL |\n+------+------+------+--------+--------+--------+--------+--------+--------+ -[LAST_VALUE2] -name=LAST_VALUE -declaration=expr -category=Information Functions -description=LAST_VALUE() evaluates all expressions and returns the last.\n \nThis is useful together with setting user variables to a\nvalue with @var:=expr, for example when you want to get data\nof rows updated/deleted without having to do two queries\nagainst the table.\n \nSince MariaDB 10.2.2, LAST_VALUE can be used as a window\nfunction.\n \nReturns NULL if no last value exists.\n \n\nCREATE TABLE t1 (a int, b int);\nINSERT INTO t1 VALUES(1,10),(2,20);\nDELETE FROM t1 WHERE a=1 AND last_value(@a:=a,@b:=b,1);\nSELECT @a,@b;\n \n+------+------+\n| @a | @b |\n+------+------+\n| 1 | 10 |\n+------+------+\n \nAs a window function:\n \nCREATE TABLE t1 (\n pk int primary key,\n a int,\n b int,\n c char(10),\n d decimal(10, 3),\n e real\n);\n \nINSERT INTO t1 VALUES\n( 1, 0, 1, ''one'', 0.1, 0.001),\n( 2, 0, 2, ''two'', 0.2, 0.002),\n( 3, 0, 3, ''three'', 0.3, 0.003),\n( 4, 1, 2, ''three'', 0.4, 0.004),\n( 5, 1, 1, ''two'', 0.5, 0.005),\n( 6, 1, 1, ''one'', 0.6, 0.006),\n( 7, 2, NULL, ''n_one'', 0.5, 0.007),\n( 8, 2, 1, ''n_two'', NULL, 0.008),\n( 9, 2, 2, NULL, 0.7, 0.009),\n(10, 2, 0, ''n_four'', 0.8, 0.010),\n(11, 2, 10, NULL, 0.9, NULL);\n \nSELECT pk, FIRST_VALUE(pk) OVER (ORDER BY pk) AS first_asc,\n LAST_VALUE(pk) OVER (ORDER BY pk) AS last_asc,\n FIRST_VALUE(pk) OVER (ORDER BY pk DESC) AS first_desc,\n LAST_VALUE(pk) OVER (ORDER BY pk DESC) AS last_desc\nFROM t1\nORDER BY pk DESC;\n \n+----+-----------+----------+------------+-----------+\n| pk | first_asc | last_asc | first_desc | last_desc |\n+----+-----------+----------+------------+-----------+\n| 11 | 1 | 11 | 11 | 11 |\n| 10 | 1 | 10 | 11 | 10 |\n| 9 | 1 | 9 | 11 | 9 |\n| 8 | 1 | 8 | 11 | 8 |\n| 7 | 1 | 7 | 11 | 7 |\n| 6 | 1 | 6 | 11 | 6 |\n| 5 | 1 | 5 | 11 | 5 |\n| 4 | 1 | 4 | 11 | 4 |\n| 3 | 1 | 3 | 11 | 3 |\n| 2 | 1 | 2 | 11 | 2 |\n| 1 | 1 | 1 | 11 | 1 |\n+----+-----------+----------+------------+-----------+\n \nCREATE OR REPLACE TABLE t1 (i int);\nINSERT INTO t1 VALUES\n(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);\n \nSELECT i,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW\nand 1 FOLLOWING) AS f_1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW and\n1 FOLLOWING) AS l_1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING\nAND 1 FOLLOWING) AS f_1p1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND\n1 FOLLOWING) AS f_1p1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING\nAND 1 PRECEDING) AS f_2p1p,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING AND\n1 PRECEDING) AS f_2p1p,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING\nAND 2 FOLLOWING) AS f_1f2f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING AND\n2 FOLLOWING) AS f_1f2f\nFROM t1;\n \n+------+------+------+--------+--------+--------+--------+--------+--------+\n| i | f_1f | l_1f | f_1p1f | f_1p1f | f_2p1p | f_2p1p |\nf_1f2f | f_1f2f |\n+------+------+------+--------+--------+--------+--------+--------+--------+\n| 1 | 1 | 2 | 1 | 2 | NULL | NULL | 2 | 3 |\n| 2 | 2 | 3 | 1 | 3 | 1 | 1 | 3 | 4 |\n| 3 | 3 | 4 | 2 | 4 | 1 | 2 | 4 | 5 |\n| 4 | 4 | 5 | 3 | 5 | 2 | 3 | 5 | 6 |\n| 5 | 5 | 6 | 4 | 6 | 3 | 4 | 6 | 7 |\n| 6 | 6 | 7 | 5 | 7 | 4 | 5 | 7 | 8 |\n| 7 | 7 | 8 | 6 | 8 | 5 | 6 | 8 | 9 |\n| 8 | 8 | 9 | 7 | 9 | 6 | 7 | 9 | 10 |\n| 9 | 9 | 10 | 8 | 10 | 7 | 8 | 10 | 10 |\n| 10 | 10 | 10 | 9 | 10 | 8 | 9 | NULL | NULL |\n+------+------+------+--------+--------+--------+--------+--------+--------+ -[LAST_VALUE3] -name=LAST_VALUE -declaration=[ PARTITION BY partition_expression ] [ ORDER BY order_list ] -category=Information Functions -description=LAST_VALUE() evaluates all expressions and returns the last.\n \nThis is useful together with setting user variables to a\nvalue with @var:=expr, for example when you want to get data\nof rows updated/deleted without having to do two queries\nagainst the table.\n \nSince MariaDB 10.2.2, LAST_VALUE can be used as a window\nfunction.\n \nReturns NULL if no last value exists.\n \n\nCREATE TABLE t1 (a int, b int);\nINSERT INTO t1 VALUES(1,10),(2,20);\nDELETE FROM t1 WHERE a=1 AND last_value(@a:=a,@b:=b,1);\nSELECT @a,@b;\n \n+------+------+\n| @a | @b |\n+------+------+\n| 1 | 10 |\n+------+------+\n \nAs a window function:\n \nCREATE TABLE t1 (\n pk int primary key,\n a int,\n b int,\n c char(10),\n d decimal(10, 3),\n e real\n);\n \nINSERT INTO t1 VALUES\n( 1, 0, 1, ''one'', 0.1, 0.001),\n( 2, 0, 2, ''two'', 0.2, 0.002),\n( 3, 0, 3, ''three'', 0.3, 0.003),\n( 4, 1, 2, ''three'', 0.4, 0.004),\n( 5, 1, 1, ''two'', 0.5, 0.005),\n( 6, 1, 1, ''one'', 0.6, 0.006),\n( 7, 2, NULL, ''n_one'', 0.5, 0.007),\n( 8, 2, 1, ''n_two'', NULL, 0.008),\n( 9, 2, 2, NULL, 0.7, 0.009),\n(10, 2, 0, ''n_four'', 0.8, 0.010),\n(11, 2, 10, NULL, 0.9, NULL);\n \nSELECT pk, FIRST_VALUE(pk) OVER (ORDER BY pk) AS first_asc,\n LAST_VALUE(pk) OVER (ORDER BY pk) AS last_asc,\n FIRST_VALUE(pk) OVER (ORDER BY pk DESC) AS first_desc,\n LAST_VALUE(pk) OVER (ORDER BY pk DESC) AS last_desc\nFROM t1\nORDER BY pk DESC;\n \n+----+-----------+----------+------------+-----------+\n| pk | first_asc | last_asc | first_desc | last_desc |\n+----+-----------+----------+------------+-----------+\n| 11 | 1 | 11 | 11 | 11 |\n| 10 | 1 | 10 | 11 | 10 |\n| 9 | 1 | 9 | 11 | 9 |\n| 8 | 1 | 8 | 11 | 8 |\n| 7 | 1 | 7 | 11 | 7 |\n| 6 | 1 | 6 | 11 | 6 |\n| 5 | 1 | 5 | 11 | 5 |\n| 4 | 1 | 4 | 11 | 4 |\n| 3 | 1 | 3 | 11 | 3 |\n| 2 | 1 | 2 | 11 | 2 |\n| 1 | 1 | 1 | 11 | 1 |\n+----+-----------+----------+------------+-----------+\n \nCREATE OR REPLACE TABLE t1 (i int);\nINSERT INTO t1 VALUES\n(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);\n \nSELECT i,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW\nand 1 FOLLOWING) AS f_1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW and\n1 FOLLOWING) AS l_1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING\nAND 1 FOLLOWING) AS f_1p1f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 PRECEDING AND\n1 FOLLOWING) AS f_1p1f,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING\nAND 1 PRECEDING) AS f_2p1p,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 2 PRECEDING AND\n1 PRECEDING) AS f_2p1p,\n FIRST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING\nAND 2 FOLLOWING) AS f_1f2f,\n LAST_VALUE(i) OVER (ORDER BY i ROWS BETWEEN 1 FOLLOWING AND\n2 FOLLOWING) AS f_1f2f\nFROM t1;\n \n+------+------+------+--------+--------+--------+--------+--------+--------+\n| i | f_1f | l_1f | f_1p1f | f_1p1f | f_2p1p | f_2p1p |\nf_1f2f | f_1f2f |\n+------+------+------+--------+--------+--------+--------+--------+--------+\n| 1 | 1 | 2 | 1 | 2 | NULL | NULL | 2 | 3 |\n| 2 | 2 | 3 | 1 | 3 | 1 | 1 | 3 | 4 |\n| 3 | 3 | 4 | 2 | 4 | 1 | 2 | 4 | 5 |\n| 4 | 4 | 5 | 3 | 5 | 2 | 3 | 5 | 6 |\n| 5 | 5 | 6 | 4 | 6 | 3 | 4 | 6 | 7 |\n| 6 | 6 | 7 | 5 | 7 | 4 | 5 | 7 | 8 |\n| 7 | 7 | 8 | 6 | 8 | 5 | 6 | 8 | 9 |\n| 8 | 8 | 9 | 7 | 9 | 6 | 7 | 9 | 10 |\n| 9 | 9 | 10 | 8 | 10 | 7 | 8 | 10 | 10 |\n| 10 | 10 | 10 | 9 | 10 | 8 | 9 | NULL | NULL |\n+------+------+------+--------+--------+--------+--------+--------+--------+ +description=LAST_VALUE() evaluates all expressions and returns the last.\n\nThis is useful together with setting user variables to a value with\n@var:=expr, for example when you want to get data of rows updated/deleted\nwithout having to do two queries against the table.\n\nLAST_VALUE can be used as a window function.\n\nReturns NULL if no last value exists.\n\nExamples\n--------\n\nCREATE TABLE t1 (a int, b int);\nINSERT INTO t1 VALUES(1,10),(2,20);\nDELETE FROM t1 WHERE a=1 AND last_value(@a:=a,@b:=b,1);\nSELECT @a,@b;\n+------+------+\n| @a | @b |\n+------+------+\n| 1 | 10 |\n+------+------+\n\nAs a window function:\n\nCREATE TABLE t1 (\n pk int primary key,\n a int,\n b int,\n c char(10),\n d decimal(10, 3),\n e real\n);\n\nINSERT INTO t1 VALUES\n( 1, 0, 1, 'one', 0.1, 0.001),\n( 2, 0, 2, 'two', 0.2, 0.002),\n( 3, 0, 3, 'three', 0.3, 0.003),\n( 4, 1, 2, 'three', 0.4, 0.004),\n( 5, 1, 1, 'two', 0.5, 0.005),\n( 6, 1, 1, 'one', 0.6, 0.006),\n( 7, 2, NULL, 'n_one', 0.5, 0.007),\n( 8, 2, 1, 'n_two', NULL, 0.008),\n( 9, 2, 2, NULL, 0.7, 0.009),\n(10, 2, 0, 'n_four', 0.8, 0.010),\n(11, 2, 10, NULL, 0.9, NULL);\n\nSELECT pk, FIRST_VALUE(pk) OVER (ORDER BY pk) AS first_asc,\n LAST_VALUE(pk) OVER (ORDER BY pk) AS last_asc,\n FIRST_VALUE(pk) OVER (ORDER BY pk DESC) AS first_desc,\n ... [LCASE] declaration=str category=String Functions -description=LCASE() is a synonym for LOWER(). +description=LCASE() is a synonym for LOWER().\n\nURL: https://mariadb.com/kb/en/lcase/ +[LEAD] +declaration=expr[, offset] +category=Window Functions +description=The LEAD function accesses data from a following row in the same result set\nwithout the need for a self-join. The specific row is determined by the offset\n(default 1), which specifies the number of rows ahead the current row to use.\nAn offset of 0 is the current row.\n\nExample\n-------\n\nCREATE TABLE t1 (pk int primary key, a int, b int, c char(10), d decimal(10,\n3), e real);\n\nINSERT INTO t1 VALUES\n ( 1, 0, 1, 'one', 0.1, 0.001),\n ( 2, 0, 2, 'two', 0.2, 0.002),\n ( 3, 0, 3, 'three', 0.3, 0.003),\n ( 4, 1, 2, 'three', 0.4, 0.004),\n ( 5, 1, 1, 'two', 0.5, 0.005),\n ( 6, 1, 1, 'one', 0.6, 0.006),\n ( 7, 2, NULL, 'n_one', 0.5, 0.007),\n ( 8, 2, 1, 'n_two', NULL, 0.008),\n ( 9, 2, 2, NULL, 0.7, 0.009),\n (10, 2, 0, 'n_four', 0.8, 0.010),\n (11, 2, 10, NULL, 0.9, NULL);\n\nSELECT pk, LEAD(pk) OVER (ORDER BY pk) AS l,\n LEAD(pk,1) OVER (ORDER BY pk) AS l1,\n LEAD(pk,2) OVER (ORDER BY pk) AS l2,\n LEAD(pk,0) OVER (ORDER BY pk) AS l0,\n LEAD(pk,-1) OVER (ORDER BY pk) AS lm1,\n LEAD(pk,-2) OVER (ORDER BY pk) AS lm2\nFROM t1;\n+----+------+------+------+------+------+------+\n| pk | l | l1 | l2 | l0 | lm1 | lm2 |\n+----+------+------+------+------+------+------+\n| 1 | 2 | 2 | 3 | 1 | NULL | NULL |\n| 2 | 3 | 3 | 4 | 2 | 1 | NULL |\n| 3 | 4 | 4 | 5 | 3 | 2 | 1 |\n| 4 | 5 | 5 | 6 | 4 | 3 | 2 |\n| 5 | 6 | 6 | 7 | 5 | 4 | 3 |\n| 6 | 7 | 7 | 8 | 6 | 5 | 4 |\n| 7 | 8 | 8 | 9 | 7 | 6 | 5 |\n| 8 | 9 | 9 | 10 | 8 | 7 | 6 |\n| 9 | 10 | 10 | 11 | 9 | 8 | 7 |\n| 10 | 11 | 11 | NULL | 10 | 9 | 8 |\n| 11 | NULL | NULL | NULL | 11 | 10 | 9 |\n+----+------+------+------+------+------+------+\n\nURL: https://mariadb.com/kb/en/lead/ [LEAST] declaration=value1,value2,... category=Comparison Operators -description=With two or more arguments, returns the smallest\n(minimum-valued)\nargument. The arguments are compared using the following\nrules:\nIf the return value is used in an INTEGER context or all\narguments are integer-valued, they are compared as integers.\nIf the return value is used in a REAL context or all\narguments are real-valued, they are compared as reals.\nIf any argument is a case-sensitive string, the arguments\nare compared as case-sensitive strings.\nIn all other cases, the arguments are compared as\ncase-insensitive strings.\n \nLEAST() returns NULL if any argument is NULL.\n \n\nSELECT LEAST(2,0);\n+------------+\n| LEAST(2,0) |\n+------------+\n| 0 |\n+------------+\n \nSELECT LEAST(34.0,3.0,5.0,767.0);\n+---------------------------+\n| LEAST(34.0,3.0,5.0,767.0) |\n+---------------------------+\n| 3.0 |\n+---------------------------+\n \nSELECT LEAST(''B'',''A'',''C'');\n+--------------------+\n| LEAST(''B'',''A'',''C'') |\n+--------------------+\n| A |\n+--------------------+ +description=With two or more arguments, returns the smallest (minimum-valued) argument.\nThe arguments are compared using the following rules:\n\n* If the return value is used in an INTEGER context or all arguments are\ninteger-valued, they are compared as integers.\n* If the return value is used in a REAL context or all arguments are\nreal-valued, they are compared as reals.\n* If any argument is a case-sensitive string, the arguments are compared as\ncase-sensitive strings.\n* In all other cases, the arguments are compared as case-insensitive strings.\n\nLEAST() returns NULL if any argument is NULL.\n\nExamples\n--------\n\nSELECT LEAST(2,0);\n+------------+\n| LEAST(2,0) |\n+------------+\n| 0 |\n+------------+\n\nSELECT LEAST(34.0,3.0,5.0,767.0);\n+---------------------------+\n| LEAST(34.0,3.0,5.0,767.0) |\n+---------------------------+\n| 3.0 |\n+---------------------------+\n\nSELECT LEAST('B','A','C');\n+--------------------+\n| LEAST('B','A','C') |\n+--------------------+\n| A |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/least/ [LEFT] declaration=str,len category=String Functions -description=Returns the leftmost len characters from the string str, or\nNULL if\nany argument is NULL.\n \n\nSELECT LEFT(''MariaDB'', 5);\n+--------------------+\n| LEFT(''MariaDB'', 5) |\n+--------------------+\n| Maria |\n+--------------------+ -[LENGTHB] +description=Returns the leftmost len characters from the string str, or NULL if any\nargument is NULL.\n\nExamples\n--------\n\nSELECT LEFT('MariaDB', 5);\n+--------------------+\n| LEFT('MariaDB', 5) |\n+--------------------+\n| Maria |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/left/ +[LENGTH] declaration=str category=String Functions -description=LENGTHB() is a synonym for LENGTH(). -[LENGTH] +description=Returns the length of the string str.\n\nIn the default mode, when Oracle mode from MariaDB 10.3 is not set, the length\nis measured in bytes. In this case, a multi-byte character counts as multiple\nbytes. This means that for a string containing five two-byte characters,\nLENGTH() returns 10, whereas CHAR_LENGTH() returns 5.\n\nWhen running Oracle mode from MariaDB 10.3, the length is measured in\ncharacters, and LENGTH is a synonym for CHAR_LENGTH().\n\nIf str is not a string value, it is converted into a string. If str is NULL,\nthe function returns NULL.\n\nExamples\n--------\n\nSELECT LENGTH('MariaDB');\n+-------------------+\n| LENGTH('MariaDB') |\n+-------------------+\n| 7 |\n+-------------------+\n\nWhen Oracle mode from MariaDB 10.3 is not set:\n\nSELECT CHAR_LENGTH('π'), LENGTH('π'), LENGTHB('π'), OCTET_LENGTH('π');\n+-------------------+--------------+---------------+--------------------+\n| CHAR_LENGTH('π') | LENGTH('π') | LENGTHB('π') | OCTET_LENGTH('π') |\n+-------------------+--------------+---------------+--------------------+\n| 1 | 2 | 2 | 2 |\n+-------------------+--------------+---------------+--------------------+\n\nIn Oracle mode from MariaDB 10.3:\n\nSELECT CHAR_LENGTH('π'), LENGTH('π'), LENGTHB('π'), OCTET_LENGTH('π');\n+-------------------+--------------+---------------+--------------------+\n| CHAR_LENGTH('π') | LENGTH('π') | LENGTHB('π') | OCTET_LENGTH('π') |\n+-------------------+--------------+---------------+--------------------+\n| 1 | 1 | 2 | 2 |\n+-------------------+--------------+---------------+--------------------+\n\nURL: https://mariadb.com/kb/en/length/ +[LENGTHB] declaration=str category=String Functions -description=Returns the length of the string str, measured in bytes. A\nmulti-byte\ncharacter counts as multiple bytes. This means that for a\nstring\ncontaining five two-byte characters, LENGTH() returns 10,\nwhereas\nCHAR_LENGTH() returns 5. \n \nIf str is not a string value, it is converted into a string.\nIf str is NULL, the function returns NULL.\n \nUntil MariaDB 10.3.1, returns MYSQL_TYPE_LONGLONG, or\nbigint(10), in all cases. From MariaDB 10.3.1, returns\nMYSQL_TYPE_LONG, or int(10), when the result would fit\nwithin 32-bits.\n \nOracle Mode\n \nWhen running Oracle mode from MariaDB 10.3, LENGTH() is a\nsynonym for CHAR_LENGTH().\n \n\nSELECT LENGTH(''MariaDB'');\n+-------------------+\n| LENGTH(''MariaDB'') |\n+-------------------+\n| 7 |\n+-------------------+\n \nSELECT LENGTH(''?'');\n+--------------+\n| LENGTH(''?'') |\n+--------------+\n| 2 |\n+--------------+\n \nIn Oracle mode from MariaDB 10.3:\n \nSELECT LENGTH(''?'');\n+--------------+\n| LENGTH(''?'') |\n+--------------+\n| 1 |\n+--------------+ +description=LENGTHB() returns the length of the given string, in bytes. When Oracle mode\nis not set, this is a synonym for LENGTH.\n\nA multi-byte character counts as multiple bytes. This means that for a string\ncontaining five two-byte characters, LENGTHB() returns 10, whereas\nCHAR_LENGTH() returns 5.\n\nIf str is not a string value, it is converted into a string. If str is NULL,\nthe function returns NULL.\n\nExamples\n--------\n\nWhen Oracle mode from MariaDB 10.3 is not set:\n\nSELECT CHAR_LENGTH('π'), LENGTH('π'), LENGTHB('π'), OCTET_LENGTH('π');\n+-------------------+--------------+---------------+--------------------+\n| CHAR_LENGTH('π') | LENGTH('π') | LENGTHB('π') | OCTET_LENGTH('π') |\n+-------------------+--------------+---------------+--------------------+\n| 1 | 2 | 2 | 2 |\n+-------------------+--------------+---------------+--------------------+\n\nIn Oracle mode from MariaDB 10.3:\n\nSELECT CHAR_LENGTH('π'), LENGTH('π'), LENGTHB('π'), OCTET_LENGTH('π');\n+-------------------+--------------+---------------+--------------------+\n| CHAR_LENGTH('π') | LENGTH('π') | LENGTHB('π') | OCTET_LENGTH('π') |\n+-------------------+--------------+---------------+--------------------+\n| 1 | 1 | 2 | 2 |\n+-------------------+--------------+---------------+--------------------+\n\nURL: https://mariadb.com/kb/en/lengthb/ +[LIMIT] +declaration=or ORDER BY +category=Data Manipulation +description=multi-table UPDATE statement. This restriction was lifted in MariaDB 10.3.2.\n\nGROUP_CONCAT\n------------\n\nStarting from MariaDB 10.3.3, it is possible to use LIMIT with GROUP_CONCAT().\n\nExamples\n--------\n\nCREATE TABLE members (name VARCHAR(20));\nINSERT INTO members VALUES('Jagdish'),('Kenny'),('Rokurou'),('Immaculada');\n\nSELECT * FROM members;\n+------------+\n| name |\n+------------+\n| Jagdish |\n| Kenny |\n| Rokurou |\n| Immaculada |\n+------------+\n\nSelect the first two names (no ordering specified):\n\nSELECT * FROM members LIMIT 2;\n+---------+\n| name |\n+---------+\n| Jagdish |\n| Kenny |\n+---------+\n\nAll the names in alphabetical order:\n\nSELECT * FROM members ORDER BY name;\n+------------+\n| name |\n+------------+\n| Immaculada |\n| Jagdish |\n| Kenny |\n| Rokurou |\n+------------+\n\nThe first two names, ordered alphabetically:\n\nSELECT * FROM members ORDER BY name LIMIT 2;\n+------------+\n| name |\n ... [LINESTRING] declaration=pt1,pt2,... category=Geometry Constructors -description=Constructs a WKB LineString value from a number of WKB Point\narguments. If any argument is not a WKB Point, the return\nvalue is\nNULL. If the number of Point arguments is less than two, the\nreturn value is NULL.\n \n\nSET @ls = ''LineString(1 1,2 2,3 3)'';\n \nSELECT AsText(EndPoint(GeomFromText(@ls)));\n+-------------------------------------+\n| AsText(EndPoint(GeomFromText(@ls))) |\n+-------------------------------------+\n| POINT(3 3) |\n+-------------------------------------+\n \nCREATE TABLE gis_line (g LINESTRING);\nINSERT INTO gis_line VALUES\n (LineFromText(''LINESTRING(0 0,0 10,10 0)'')),\n (LineStringFromText(''LINESTRING(10 10,20 10,20 20,10 20,10\n10)'')),\n (LineStringFromWKB(AsWKB(LineString(Point(10, 10),\nPoint(40, 10))))); +description=Constructs a WKB LineString value from a number of WKB Point arguments. If any\nargument is not a WKB Point, the return value is NULL. If the number of Point\narguments is less than two, the return value is NULL.\n\nExamples\n--------\n\nSET @ls = 'LineString(1 1,2 2,3 3)';\n\nSELECT AsText(EndPoint(GeomFromText(@ls)));\n+-------------------------------------+\n| AsText(EndPoint(GeomFromText(@ls))) |\n+-------------------------------------+\n| POINT(3 3) |\n+-------------------------------------+\n\nCREATE TABLE gis_line (g LINESTRING);\nINSERT INTO gis_line VALUES\n (LineFromText('LINESTRING(0 0,0 10,10 0)')),\n (LineStringFromText('LINESTRING(10 10,20 10,20 20,10 20,10 10)')),\n (LineStringFromWKB(AsWKB(LineString(Point(10, 10), Point(40, 10)))));\n\nURL: https://mariadb.com/kb/en/linestring/ [LN] declaration=X category=Numeric Functions -description=Returns the natural logarithm of X; that is, the base-e\nlogarithm of X.\nIf X is less than or equal to 0, or NULL, then NULL is\nreturned.\n \nThe inverse of this function is EXP().\n \n\nSELECT LN(2);\n+-------------------+\n| LN(2) |\n+-------------------+\n| 0.693147180559945 |\n+-------------------+\n \nSELECT LN(-2);\n+--------+\n| LN(-2) |\n+--------+\n| NULL |\n+--------+ +description=Returns the natural logarithm of X; that is, the base-e logarithm of X. If X\nis less than or equal to 0, or NULL, then NULL is returned.\n\nThe inverse of this function is EXP().\n\nExamples\n--------\n\nSELECT LN(2);\n+-------------------+\n| LN(2) |\n+-------------------+\n| 0.693147180559945 |\n+-------------------+\n\nSELECT LN(-2);\n+--------+\n| LN(-2) |\n+--------+\n| NULL |\n+--------+\n\nURL: https://mariadb.com/kb/en/ln/ [LOAD_FILE] declaration=file_name category=String Functions -description=Reads the file and returns the file contents as a string. To\nuse this function, the file must be located on the server\nhost, you must specify the full path name to the file, and\nyou must have the FILE privilege. The file must be readable\nby all and it must be less than the size, in bytes, of the\nmax_allowed_packet system variable. If the secure_file_priv\nsystem variable is set to a non-empty directory name, the\nfile to be loaded must be located in that directory.\n \nIf the file does not exist or cannot be read because one of\nthe preceding conditions is not satisfied, the function\nreturns NULL.\n \nSince MariaDB 5.1, the character_set_filesystem system\nvariable has controlled interpretation of file names that\nare given as literal strings.\n \nStatements using the LOAD_FILE() function are not safe for\nstatement based replication. This is because the slave will\nexecute the LOAD_FILE() command itself. If the file doesn''t\nexist on the slave, the function will return NULL.\n \n\nUPDATE t SET blob_col=LOAD_FILE(''/tmp/picture'') WHERE\nid=1; -[LOCATE1] -name=LOCATE +description=Reads the file and returns the file contents as a string. To use this\nfunction, the file must be located on the server host, you must specify the\nfull path name to the file, and you must have the FILE privilege. The file\nmust be readable by all and it must be less than the size, in bytes, of the\nmax_allowed_packet system variable. If the secure_file_priv system variable is\nset to a non-empty directory name, the file to be loaded must be located in\nthat directory.\n\nIf the file does not exist or cannot be read because one of the preceding\nconditions is not satisfied, the function returns NULL.\n\nSince MariaDB 5.1, the character_set_filesystem system variable has controlled\ninterpretation of file names that are given as literal strings.\n\nStatements using the LOAD_FILE() function are not safe for statement based\nreplication. This is because the slave will execute the LOAD_FILE() command\nitself. If the file doesn't exist on the slave, the function will return NULL.\n\nExamples\n--------\n\nUPDATE t SET blob_col=LOAD_FILE('/tmp/picture') WHERE id=1;\n\nURL: https://mariadb.com/kb/en/load_file/ +[LOCALTIME] +declaration=[precision] +category=Date and Time Functions +description=LOCALTIME and LOCALTIME() are synonyms for NOW().\n\nURL: https://mariadb.com/kb/en/localtime/ +[LOCALTIMESTAMP] +declaration=[precision] +category=Date and Time Functions +description=LOCALTIMESTAMP and LOCALTIMESTAMP() are synonyms for NOW().\n\nURL: https://mariadb.com/kb/en/localtimestamp/ +[LOCATE] declaration=substr,str category=String Functions -description=The first syntax returns the position of the first\noccurrence of\nsubstring substr in string str. The second syntax returns\nthe position\nof the first occurrence of substring substr in string str,\nstarting at\nposition pos. Returns 0 if substr is not in str.\n \nLOCATE() performs a case-insensitive search.\n \nIf any argument is NULL, returns NULL.\n \nINSTR() is a synonym of LOCATE() without the third argument.\n \n\nSELECT LOCATE(''bar'', ''foobarbar'');\n+----------------------------+\n| LOCATE(''bar'', ''foobarbar'') |\n+----------------------------+\n| 4 |\n+----------------------------+\n \nSELECT LOCATE(''My'', ''Maria'');\n+-----------------------+\n| LOCATE(''My'', ''Maria'') |\n+-----------------------+\n| 0 |\n+-----------------------+\n \nSELECT LOCATE(''bar'', ''foobarbar'', 5);\n+-------------------------------+\n| LOCATE(''bar'', ''foobarbar'', 5) |\n+-------------------------------+\n| 7 |\n+-------------------------------+ -[LOCATE2] -name=LOCATE -declaration=substr,str,pos -category=String Functions -description=The first syntax returns the position of the first\noccurrence of\nsubstring substr in string str. The second syntax returns\nthe position\nof the first occurrence of substring substr in string str,\nstarting at\nposition pos. Returns 0 if substr is not in str.\n \nLOCATE() performs a case-insensitive search.\n \nIf any argument is NULL, returns NULL.\n \nINSTR() is a synonym of LOCATE() without the third argument.\n \n\nSELECT LOCATE(''bar'', ''foobarbar'');\n+----------------------------+\n| LOCATE(''bar'', ''foobarbar'') |\n+----------------------------+\n| 4 |\n+----------------------------+\n \nSELECT LOCATE(''My'', ''Maria'');\n+-----------------------+\n| LOCATE(''My'', ''Maria'') |\n+-----------------------+\n| 0 |\n+-----------------------+\n \nSELECT LOCATE(''bar'', ''foobarbar'', 5);\n+-------------------------------+\n| LOCATE(''bar'', ''foobarbar'', 5) |\n+-------------------------------+\n| 7 |\n+-------------------------------+ -[LOG10] +description=The first syntax returns the position of the first occurrence of substring\nsubstr in string str. The second syntax returns the position of the first\noccurrence of substring substr in string str, starting at position pos.\nReturns 0 if substr is not in str.\n\nLOCATE() performs a case-insensitive search.\n\nIf any argument is NULL, returns NULL.\n\nINSTR() is the same as the two-argument form of LOCATE(), except that the\norder of the arguments is reversed.\n\nExamples\n--------\n\nSELECT LOCATE('bar', 'foobarbar');\n+----------------------------+\n| LOCATE('bar', 'foobarbar') |\n+----------------------------+\n| 4 |\n+----------------------------+\n\nSELECT LOCATE('My', 'Maria');\n+-----------------------+\n| LOCATE('My', 'Maria') |\n+-----------------------+\n| 0 |\n+-----------------------+\n\nSELECT LOCATE('bar', 'foobarbar', 5);\n+-------------------------------+\n| LOCATE('bar', 'foobarbar', 5) |\n+-------------------------------+\n| 7 |\n+-------------------------------+\n\nURL: https://mariadb.com/kb/en/locate/ +[LOG] declaration=X category=Numeric Functions -description=Returns the base-10 logarithm of X.\n \n\nSELECT LOG10(2);\n+-------------------+\n| LOG10(2) |\n+-------------------+\n| 0.301029995663981 |\n+-------------------+\n \nSELECT LOG10(100);\n+------------+\n| LOG10(100) |\n+------------+\n| 2 |\n+------------+\n \nSELECT LOG10(-100);\n+-------------+\n| LOG10(-100) |\n+-------------+\n| NULL |\n+-------------+ -[LOG1] -name=LOG +description=If called with one parameter, this function returns the natural logarithm of\nX. If X is less than or equal to 0, then NULL is returned.\n\nIf called with two parameters, it returns the logarithm of X to the base B. If\nB is <= 1 or X <= 0, the function returns NULL.\n\nIf any argument is NULL, the function returns NULL.\n\nThe inverse of this function (when called with a single argument) is the EXP()\nfunction.\n\nExamples\n--------\n\nLOG(X):\n\nSELECT LOG(2);\n+-------------------+\n| LOG(2) |\n+-------------------+\n| 0.693147180559945 |\n+-------------------+\n\nSELECT LOG(-2);\n+---------+\n| LOG(-2) |\n+---------+\n| NULL |\n+---------+\n\nLOG(B,X)\n\nSELECT LOG(2,16);\n+-----------+\n| LOG(2,16) |\n+-----------+\n| 4 |\n+-----------+\n\nSELECT LOG(3,27);\n+-----------+\n| LOG(3,27) |\n+-----------+\n| 3 |\n+-----------+\n\nSELECT LOG(3,1);\n+----------+\n| LOG(3,1) |\n+----------+\n ... +[LOG10] declaration=X category=Numeric Functions -description=If called with one parameter, this function returns the\nnatural\nlogarithm of X. If X is less than or equal to 0, then NULL\nis\nreturned.\n \nIf called with two parameters, it returns the logarithm of X\nto the base B. If B is +description=Returns the base-10 logarithm of X.\n\nExamples\n--------\n\nSELECT LOG10(2);\n+-------------------+\n| LOG10(2) |\n+-------------------+\n| 0.301029995663981 |\n+-------------------+\n\nSELECT LOG10(100);\n+------------+\n| LOG10(100) |\n+------------+\n| 2 |\n+------------+\n\nSELECT LOG10(-100);\n+-------------+\n| LOG10(-100) |\n+-------------+\n| NULL |\n+-------------+\n\nURL: https://mariadb.com/kb/en/log10/ [LOG2] declaration=X category=Numeric Functions -description=Returns the base-2 logarithm of X.\n \n\nSELECT LOG2(4398046511104);\n+---------------------+\n| LOG2(4398046511104) |\n+---------------------+\n| 42 |\n+---------------------+\n \nSELECT LOG2(65536);\n+-------------+\n| LOG2(65536) |\n+-------------+\n| 16 |\n+-------------+\n \nSELECT LOG2(-100);\n+------------+\n| LOG2(-100) |\n+------------+\n| NULL |\n+------------+ -[LOG2] -name=LOG -declaration=B,X -category=Numeric Functions -description=If called with one parameter, this function returns the\nnatural\nlogarithm of X. If X is less than or equal to 0, then NULL\nis\nreturned.\n \nIf called with two parameters, it returns the logarithm of X\nto the base B. If B is +description=Returns the base-2 logarithm of X.\n\nExamples\n--------\n\nSELECT LOG2(4398046511104);\n+---------------------+\n| LOG2(4398046511104) |\n+---------------------+\n| 42 |\n+---------------------+\n\nSELECT LOG2(65536);\n+-------------+\n| LOG2(65536) |\n+-------------+\n| 16 |\n+-------------+\n\nSELECT LOG2(-100);\n+------------+\n| LOG2(-100) |\n+------------+\n| NULL |\n+------------+\n\nURL: https://mariadb.com/kb/en/log2/ [LOWER] declaration=str category=String Functions -description=Returns the string str with all characters changed to\nlowercase\naccording to the current character set mapping. The default\nis latin1\n(cp1252 West European).\n \n\n SELECT LOWER(''QUADRATICALLY'');\n+------------------------+\n| LOWER(''QUADRATICALLY'') |\n+------------------------+\n| quadratically |\n+------------------------+\n \nLOWER() (and UPPER()) are ineffective when applied to binary\nstrings (BINARY, VARBINARY, BLOB). \nTo perform lettercase conversion, CONVERT the string to a\nnon-binary string:\n \nSET @str = BINARY ''North Carolina'';\n \nSELECT LOWER(@str), LOWER(CONVERT(@str USING latin1));\n+----------------+-----------------------------------+\n| LOWER(@str) | LOWER(CONVERT(@str USING latin1)) |\n+----------------+-----------------------------------+\n| North Carolina | north carolina |\n+----------------+-----------------------------------+ +description=Returns the string str with all characters changed to lowercase according to\nthe current character set mapping. The default is latin1 (cp1252 West\nEuropean).\n\nLCASE is a synonym for LOWER\n\nExamples\n--------\n\nSELECT LOWER('QUADRATICALLY');\n+------------------------+\n| LOWER('QUADRATICALLY') |\n+------------------------+\n| quadratically |\n+------------------------+\n\nLOWER() (and UPPER()) are ineffective when applied to binary strings (BINARY,\nVARBINARY, BLOB). To perform lettercase conversion, CONVERT the string to a\nnon-binary string:\n\nSET @str = BINARY 'North Carolina';\n\nSELECT LOWER(@str), LOWER(CONVERT(@str USING latin1));\n+----------------+-----------------------------------+\n| LOWER(@str) | LOWER(CONVERT(@str USING latin1)) |\n+----------------+-----------------------------------+\n| North Carolina | north carolina |\n+----------------+-----------------------------------+\n\nURL: https://mariadb.com/kb/en/lower/ [LPAD] declaration=str, len [,padstr] category=String Functions -description=Returns the string str, left-padded with the string padstr\nto a length\nof len characters. If str is longer than len, the return\nvalue is\nshortened to len characters. If padstr is omitted, the LPAD\nfunction pads spaces.\n \nPrior to MariaDB 10.3.1, the padstr parameter was mandatory.\n \nReturns NULL if given a NULL argument. If the result is\nempty (zero length), returns either an empty string or, from\nMariaDB 10.3.6 with SQL_MODE=Oracle, NULL.\n \nThe Oracle mode version of the function can be accessed\noutside of Oracle mode by using LPAD_ORACLE as the function\nname.\n \n\nSELECT LPAD(''hello'',10,''.'');\n+----------------------+\n| LPAD(''hello'',10,''.'') |\n+----------------------+\n| .....hello |\n+----------------------+\n \nSELECT LPAD(''hello'',2,''.'');\n+---------------------+\n| LPAD(''hello'',2,''.'') |\n+---------------------+\n| he |\n+---------------------+\n \nFrom MariaDB 10.3.1, with the pad string defaulting to\nspace.\n \nSELECT LPAD(''hello'',10);\n+------------------+\n| LPAD(''hello'',10) |\n+------------------+\n| hello |\n+------------------+\n \nOracle mode version from MariaDB 10.3.6:\n \nSELECT LPAD('''',0),LPAD_ORACLE('''',0);\n+------------+-------------------+\n| LPAD('''',0) | LPAD_ORACLE('''',0) |\n+------------+-------------------+\n| | NULL |\n+------------+-------------------+ +description=Returns the string str, left-padded with the string padstr to a length of len\ncharacters. If str is longer than len, the return value is shortened to len\ncharacters. If padstr is omitted, the LPAD function pads spaces.\n\nPrior to MariaDB 10.3.1, the padstr parameter was mandatory.\n\nReturns NULL if given a NULL argument. If the result is empty (zero length),\nreturns either an empty string or, from MariaDB 10.3.6 with SQL_MODE=Oracle,\nNULL.\n\nThe Oracle mode version of the function can be accessed outside of Oracle mode\nby using LPAD_ORACLE as the function name.\n\nExamples\n--------\n\nSELECT LPAD('hello',10,'.');\n+----------------------+\n| LPAD('hello',10,'.') |\n+----------------------+\n| .....hello |\n+----------------------+\n\nSELECT LPAD('hello',2,'.');\n+---------------------+\n| LPAD('hello',2,'.') |\n+---------------------+\n| he |\n+---------------------+\n\nFrom MariaDB 10.3.1, with the pad string defaulting to space.\n\nSELECT LPAD('hello',10);\n+------------------+\n| LPAD('hello',10) |\n+------------------+\n| hello |\n+------------------+\n\nOracle mode version from MariaDB 10.3.6:\n\nSELECT LPAD('',0),LPAD_ORACLE('',0);\n+------------+-------------------+\n| LPAD('',0) | LPAD_ORACLE('',0) |\n+------------+-------------------+\n| | NULL |\n+------------+-------------------+\n\nURL: https://mariadb.com/kb/en/lpad/ [LTRIM] declaration=str category=String Functions -description=Returns the string str with leading space characters\nremoved.\n \nReturns NULL if given a NULL argument. If the result is\nempty, returns either an empty string, or, from MariaDB\n10.3.6 with SQL_MODE=Oracle, NULL.\n \nThe Oracle mode version of the function can be accessed\noutside of Oracle mode by using LTRIM_ORACLE as the function\nname.\n \n\nSELECT QUOTE(LTRIM('' MariaDB ''));\n+-------------------------------+\n| QUOTE(LTRIM('' MariaDB '')) |\n+-------------------------------+\n| ''MariaDB '' |\n+-------------------------------+\n \nOracle mode version from MariaDB 10.3.6:\n \nSELECT LTRIM(''''),LTRIM_ORACLE('''');\n+-----------+------------------+\n| LTRIM('''') | LTRIM_ORACLE('''') |\n+-----------+------------------+\n| | NULL |\n+-----------+------------------+ +description=Returns the string str with leading space characters removed.\n\nReturns NULL if given a NULL argument. If the result is empty, returns either\nan empty string, or, from MariaDB 10.3.6 with SQL_MODE=Oracle, NULL.\n\nThe Oracle mode version of the function can be accessed outside of Oracle mode\nby using LTRIM_ORACLE as the function name.\n\nExamples\n--------\n\nSELECT QUOTE(LTRIM(' MariaDB '));\n+-------------------------------+\n| QUOTE(LTRIM(' MariaDB ')) |\n+-------------------------------+\n| 'MariaDB ' |\n+-------------------------------+\n\nOracle mode version from MariaDB 10.3.6:\n\nSELECT LTRIM(''),LTRIM_ORACLE('');\n+-----------+------------------+\n| LTRIM('') | LTRIM_ORACLE('') |\n+-----------+------------------+\n| | NULL |\n+-----------+------------------+\n\nURL: https://mariadb.com/kb/en/ltrim/ [MAKEDATE] declaration=year,dayofyear category=Date and Time Functions -description=Returns a date, given year and day-of-year values. dayofyear\nmust be\ngreater than 0 or the result is NULL.\n \n\nSELECT MAKEDATE(2011,31), MAKEDATE(2011,32);\n+-------------------+-------------------+\n| MAKEDATE(2011,31) | MAKEDATE(2011,32) |\n+-------------------+-------------------+\n| 2011-01-31 | 2011-02-01 |\n+-------------------+-------------------+\n \nSELECT MAKEDATE(2011,365), MAKEDATE(2014,365);\n+--------------------+--------------------+\n| MAKEDATE(2011,365) | MAKEDATE(2014,365) |\n+--------------------+--------------------+\n| 2011-12-31 | 2014-12-31 |\n+--------------------+--------------------+\n \nSELECT MAKEDATE(2011,0);\n+------------------+\n| MAKEDATE(2011,0) |\n+------------------+\n| NULL |\n+------------------+ +description=Returns a date, given year and day-of-year values. dayofyear must be greater\nthan 0 or the result is NULL.\n\nExamples\n--------\n\nSELECT MAKEDATE(2011,31), MAKEDATE(2011,32);\n+-------------------+-------------------+\n| MAKEDATE(2011,31) | MAKEDATE(2011,32) |\n+-------------------+-------------------+\n| 2011-01-31 | 2011-02-01 |\n+-------------------+-------------------+\n\nSELECT MAKEDATE(2011,365), MAKEDATE(2014,365);\n+--------------------+--------------------+\n| MAKEDATE(2011,365) | MAKEDATE(2014,365) |\n+--------------------+--------------------+\n| 2011-12-31 | 2014-12-31 |\n+--------------------+--------------------+\n\nSELECT MAKEDATE(2011,0);\n+------------------+\n| MAKEDATE(2011,0) |\n+------------------+\n| NULL |\n+------------------+\n\nURL: https://mariadb.com/kb/en/makedate/ [MAKETIME] declaration=hour,minute,second category=Date and Time Functions -description=Returns a time value calculated from the hour, minute, and\nsecond arguments.\n \nIf minute or second are out of the range 0 to 60, NULL is\nreturned. The hour can be in the range -838 to 838, outside\nof which the value is truncated with a warning.\n \n\nSELECT MAKETIME(13,57,33);\n+--------------------+\n| MAKETIME(13,57,33) |\n+--------------------+\n| 13:57:33 |\n+--------------------+\n \nSELECT MAKETIME(-13,57,33);\n+---------------------+\n| MAKETIME(-13,57,33) |\n+---------------------+\n| -13:57:33 |\n+---------------------+\n \nSELECT MAKETIME(13,67,33);\n+--------------------+\n| MAKETIME(13,67,33) |\n+--------------------+\n| NULL |\n+--------------------+\n \nSELECT MAKETIME(-1000,57,33);\n+-----------------------+\n| MAKETIME(-1000,57,33) |\n+-----------------------+\n| -838:59:59 |\n+-----------------------+\n1 row in set, 1 warning (0.00 sec)\n \nSHOW WARNINGS;\n \n+---------+------+-----------------------------------------------+\n| Level | Code | Message |\n+---------+------+-----------------------------------------------+\n| Warning | 1292 | Truncated incorrect time value:\n''-1000:57:33'' |\n+---------+------+-----------------------------------------------+ +description=Returns a time value calculated from the hour, minute, and second arguments.\n\nIf minute or second are out of the range 0 to 60, NULL is returned. The hour\ncan be in the range -838 to 838, outside of which the value is truncated with\na warning.\n\nExamples\n--------\n\nSELECT MAKETIME(13,57,33);\n+--------------------+\n| MAKETIME(13,57,33) |\n+--------------------+\n| 13:57:33 |\n+--------------------+\n\nSELECT MAKETIME(-13,57,33);\n+---------------------+\n| MAKETIME(-13,57,33) |\n+---------------------+\n| -13:57:33 |\n+---------------------+\n\nSELECT MAKETIME(13,67,33);\n+--------------------+\n| MAKETIME(13,67,33) |\n+--------------------+\n| NULL |\n+--------------------+\n\nSELECT MAKETIME(-1000,57,33);\n+-----------------------+\n| MAKETIME(-1000,57,33) |\n+-----------------------+\n| -838:59:59 |\n+-----------------------+\n1 row in set, 1 warning (0.00 sec)\n\nSHOW WARNINGS;\n+---------+------+-----------------------------------------------+\n| Level | Code | Message |\n+---------+------+-----------------------------------------------+\n| Warning | 1292 | Truncated incorrect time value: '-1000:57:33' |\n+---------+------+-----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/maketime/ [MAKE_SET] declaration=bits,str1,str2,... category=String Functions -description=Returns a set value (a string containing substrings\nseparated by ","\ncharacters) consisting of the strings that have the\ncorresponding bit\nin bits set. str1 corresponds to bit 0, str2 to bit 1, and\nso on. NULL\nvalues in str1, str2, ... are not appended to the result.\n \n\nSELECT MAKE_SET(1,''a'',''b'',''c'');\n+-------------------------+\n| MAKE_SET(1,''a'',''b'',''c'') |\n+-------------------------+\n| a |\n+-------------------------+\n \nSELECT MAKE_SET(1 | 4,''hello'',''nice'',''world'');\n+----------------------------------------+\n| MAKE_SET(1 | 4,''hello'',''nice'',''world'') |\n+----------------------------------------+\n| hello,world |\n+----------------------------------------+\n \nSELECT MAKE_SET(1 | 4,''hello'',''nice'',NULL,''world'');\n+---------------------------------------------+\n| MAKE_SET(1 | 4,''hello'',''nice'',NULL,''world'') |\n+---------------------------------------------+\n| hello |\n+---------------------------------------------+\n \nSELECT QUOTE(MAKE_SET(0,''a'',''b'',''c''));\n+--------------------------------+\n| QUOTE(MAKE_SET(0,''a'',''b'',''c'')) |\n+--------------------------------+\n| '''' |\n+--------------------------------+ +description=Returns a set value (a string containing substrings separated by ","\ncharacters) consisting of the strings that have the corresponding bit in bits\nset. str1 corresponds to bit 0, str2 to bit 1, and so on. NULL values in str1,\nstr2, ... are not appended to the result.\n\nExamples\n--------\n\nSELECT MAKE_SET(1,'a','b','c');\n+-------------------------+\n| MAKE_SET(1,'a','b','c') |\n+-------------------------+\n| a |\n+-------------------------+\n\nSELECT MAKE_SET(1 | 4,'hello','nice','world');\n+----------------------------------------+\n| MAKE_SET(1 | 4,'hello','nice','world') |\n+----------------------------------------+\n| hello,world |\n+----------------------------------------+\n\nSELECT MAKE_SET(1 | 4,'hello','nice',NULL,'world');\n+---------------------------------------------+\n| MAKE_SET(1 | 4,'hello','nice',NULL,'world') |\n+---------------------------------------------+\n| hello |\n+---------------------------------------------+\n\nSELECT QUOTE(MAKE_SET(0,'a','b','c'));\n+--------------------------------+\n| QUOTE(MAKE_SET(0,'a','b','c')) |\n+--------------------------------+\n| '' |\n+--------------------------------+\n\nURL: https://mariadb.com/kb/en/make_set/ [MASTER_GTID_WAIT] declaration=gtid-list[, timeout category=Miscellaneous Functions -description=This function takes a string containing a comma-separated\nlist of global transaction id''s\n(similar to the value of, for example, gtid_binlog_pos). It\nwaits until the value of gtid_slave_pos has the same or\nhigher seq_no within all replication domains specified in\nthe gtid-list; in other words, it waits until the slave has\nreached the specified GTID position.\n \nAn optional second argument gives a timeout in seconds. If\nthe timeout\nexpires before the specified GTID position is reached, then\nthe function\nreturns -1. Passing NULL or a negative number for the\ntimeout means no timeout, and the function will wait\nindefinitely.\n \n If the wait completes without a timeout, 0 is returned.\nPassing NULL for the\n gtid-list makes the function return NULL immediately,\nwithout waiting.\n \nThe gtid-list may be the empty string, in which case\nMASTER_GTID_WAIT()\nreturns immediately. If the gtid-list contains fewer domains\nthan\ngtid_slave_pos, then only those domains are waited upon. If\ngtid-list\ncontains a domain that is not present in @@gtid_slave_pos,\nthen\nMASTER_GTID_WAIT() will wait until an event containing such\ndomain_id arrives\non the slave (or until timed out or killed).\n \nMASTER_GTID_WAIT() can be useful to ensure that a slave has\ncaught up to\na master. Simply take the value of gtid_binlog_pos on the\nmaster, and use it in a MASTER_GTID_WAIT() call on the\nslave; when the call completes, the slave\nwill have caught up with that master position.\n \nMASTER_GTID_WAIT() can also be used in client applications\ntogether with the\nlast_gtid session variable. This is useful in a\nread-scaleout replication setup, where the application\nwrites to a single master but divides the\nreads out to a number of slaves to distribute the load. In\nsuch a setup, there\nis a risk that an application could first do an update on\nthe master, and then\na bit later do a read on a slave, and if the slave is not\nfast enough, the\ndata read from the slave might not include the update just\nmade, possibly\nconfusing the application and/or the end-user. One way to\navoid this is to\nrequest the value of last_gtid on the master just after the\nupdate. Then\nbefore doing the read on the slave, do a MASTER_GTID_WAIT()\non the value\nobtained from the master; this will ensure that the read is\nnot performed\nuntil the slave has replicated sufficiently far for the\nupdate to have become\nvisible.\n \nNote that MASTER_GTID_WAIT() can be used even if the slave\nis configured not\nto use GTID for connections (CHANGE MASTER TO\nmaster_use_gtid=no). This is\nbecause from MariaDB 10, GTIDs are always logged on the\nmaster server, and\nalways recorded on the slave servers.\n \nDifferences to MASTER_POS_WAIT()\n \nMASTER_GTID_WAIT() is global; it waits for any master\nconnection to reach\n the specified GTID position. MASTER_POS_WAIT() works only\nagainst a\n specific connection. This also means that while\nMASTER_POS_WAIT() aborts if\n its master connection is terminated with STOP SLAVE or due\nto an error,\n MASTER_GTID_WAIT() continues to wait while slaves are\nstopped.\n \nMASTER_GTID_WAIT() can take its timeout as a floating-point\nvalue, so a\n timeout in fractional seconds is supported, eg.\nMASTER_GTID_WAIT("0-1-100",\n 0.5). (The minimum wait is one microsecond, 0.000001\nseconds).\n \nMASTER_GTID_WAIT() allows one to specify a timeout of zero\nin order to do a\n non-blocking check to see if the slaves have progressed to\na specific GTID position\n (MASTER_POS_WAIT() takes a zero timeout as meaning an\ninfinite wait). To do\n an infinite MASTER_GTID_WAIT(), specify a negative timeout,\nor omit the\n timeout argument.\n \nMASTER_GTID_WAIT() does not return the number of events\nexecuted since the\n wait started, nor does it return NULL if a slave thread is\nstopped. It\n always returns either 0 for successful wait completed, or\n-1 for timeout\n reached (or NULL if the specified gtid-pos is NULL).\n \nSince MASTER_GTID_WAIT() looks only at the seq_no part of\nthe GTIDs, not the\nserver_id, care is needed if a slave becomes diverged from\nanother server so\nthat two different GTIDs with the same seq_no (in the same\ndomain) arrive at\nthe same server. This situation is in any case best avoided;\nsetting\ngtid_strict_mode is recommended, as this will prevent any\nsuch out-of-order sequence numbers from ever being\nreplicated on a slave. +description=This function takes a string containing a comma-separated list of global\ntransaction id's (similar to the value of, for example, gtid_binlog_pos). It\nwaits until the value of gtid_slave_pos has the same or higher seq_no within\nall replication domains specified in the gtid-list; in other words, it waits\nuntil the slave has reached the specified GTID position.\n\nAn optional second argument gives a timeout in seconds. If the timeout expires\nbefore the specified GTID position is reached, then the function returns -1.\nPassing NULL or a negative number for the timeout means no timeout, and the\nfunction will wait indefinitely.\n\nIf the wait completes without a timeout, 0 is returned. Passing NULL for the\ngtid-list makes the function return NULL immediately, without waiting.\n\nThe gtid-list may be the empty string, in which case MASTER_GTID_WAIT()\nreturns immediately. If the gtid-list contains fewer domains than\ngtid_slave_pos, then only those domains are waited upon. If gtid-list contains\na domain that is not present in @@gtid_slave_pos, then MASTER_GTID_WAIT() will\nwait until an event containing such domain_id arrives on the slave (or until\ntimed out or killed).\n\nMASTER_GTID_WAIT() can be useful to ensure that a slave has caught up to a\nmaster. Simply take the value of gtid_binlog_pos on the master, and use it in\na MASTER_GTID_WAIT() call on the slave; when the call completes, the slave\nwill have caught up with that master position.\n\nMASTER_GTID_WAIT() can also be used in client applications together with the\nlast_gtid session variable. This is useful in a read-scaleout replication\nsetup, where the application writes to a single master but divides the reads\nout to a number of slaves to distribute the load. In such a setup, there is a\nrisk that an application could first do an update on the master, and then a\nbit later do a read on a slave, and if the slave is not fast enough, the data\nread from the slave might not include the update just made, possibly confusing\nthe application and/or the end-user. One way to avoid this is to request the\nvalue of last_gtid on the master just after the update. Then before doing the\nread on the slave, do a MASTER_GTID_WAIT() on the value obtained from the\nmaster; this will ensure that the read is not performed until the slave has\nreplicated sufficiently far for the update to have become visible.\n\nNote that MASTER_GTID_WAIT() can be used even if the slave is configured not\nto use GTID for connections (CHANGE MASTER TO master_use_gtid=no). This is\nbecause from MariaDB 10, GTIDs are always logged on the master server, and\nalways recorded on the slave servers.\n\nDifferences to MASTER_POS_WAIT()\n--------------------------------\n\n* MASTER_GTID_WAIT() is global; it waits for any master connection to reach\n the specified GTID position. MASTER_POS_WAIT() works only against a\n specific connection. This also means that while MASTER_POS_WAIT() aborts if\n ... [MASTER_POS_WAIT] declaration=log_name,log_pos[,timeout,["connection_name"]] category=Miscellaneous Functions -description=This function is useful in replication for controlling\nmaster/slave synchronization. It blocks until the slave has\nread and applied all updates up to the specified position\n(log_name,log_pos) in the master log. The return value is\nthe number of log events the slave had to wait for to\nadvance to the specified position. The function returns NULL\nif\nthe slave SQL thread is not started, the slave''s master\ninformation is not\ninitialized, the arguments are incorrect, or an error\noccurs. It returns -1 if\nthe timeout has been exceeded. If the slave SQL thread stops\nwhile\n MASTER_POS_WAIT() is waiting, the function returns NULL. If\nthe slave is past the specified position, the function\nreturns immediately.\n \nIf a timeout value is specified, MASTER_POS_WAIT() stops\nwaiting when timeout seconds have elapsed. timeout must be\ngreater than 0; a\nzero or negative timeout means no timeout.\n \nThe connection_name is used when you are using\nmulti-source-replication. If you don''t specify it, it''s\nset to the value of the default_master_connection system\nvariable.\n \nStatements using the MASTER_POS_WAIT() function are not safe\nfor replication. +description=This function is useful in replication for controlling primary/replica\nsynchronization. It blocks until the replica has read and applied all updates\nup to the specified position (log_name,log_pos) in the primary log. The return\nvalue is the number of log events the replica had to wait for to advance to\nthe specified position. The function returns NULL if the replica SQL thread is\nnot started, the replica's primary information is not initialized, the\narguments are incorrect, or an error occurs. It returns -1 if the timeout has\nbeen exceeded. If the replica SQL thread stops while MASTER_POS_WAIT() is\nwaiting, the function returns NULL. If the replica is past the specified\nposition, the function returns immediately.\n\nIf a timeout value is specified, MASTER_POS_WAIT() stops waiting when timeout\nseconds have elapsed. timeout must be greater than 0; a zero or negative\ntimeout means no timeout.\n\nThe connection_name is used when you are using multi-source-replication. If\nyou don't specify it, it's set to the value of the default_master_connection\nsystem variable.\n\nStatements using the MASTER_POS_WAIT() function are not safe for\nstatement-based replication.\n\nURL: https://mariadb.com/kb/en/master_pos_wait/ [MAX] declaration=[DISTINCT] expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the largest, or maximum, value of expr. MAX() can\nalso take a string\nargument in which case it returns the maximum string value.\nThe DISTINCT\nkeyword can be used to find the maximum of the distinct\nvalues of expr,\nhowever, this produces the same result as omitting DISTINCT.\n \nNote that SET and ENUM fields are currently compared by\ntheir string value rather than their relative position in\nthe set, so MAX() may produce a different highest result\nthan ORDER BY DESC.\n \nIt is an aggregate function, and so can be used with the\nGROUP BY clause.\n \nFrom MariaDB 10.2.2, MAX() can be used as a window function.\n \nMAX() returns NULL if there were no matching rows.\n \n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score\nTINYINT); \n \nINSERT INTO student VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87), (''Tatiana'', ''Tuning'', 83);\n \nSELECT name, MAX(score) FROM student GROUP BY name;\n \n+---------+------------+\n| name | MAX(score) |\n+---------+------------+\n| Chun | 75 |\n| Esben | 43 |\n| Kaolin | 88 |\n| Tatiana | 87 |\n+---------+------------+\n \nMAX string:\n \nSELECT MAX(name) FROM student;\n \n+-----------+\n| MAX(name) |\n+-----------+\n| Tatiana |\n+-----------+\n \nBe careful to avoid this common mistake, not grouping\ncorrectly and returning mismatched data: \n \nSELECT name,test,MAX(SCORE) FROM student;\n \n+------+------+------------+\n| name | test | MAX(SCORE) |\n+------+------+------------+\n| Chun | SQL | 88 |\n+------+------+------------+\n \nDifference between ORDER BY DESC and MAX():\n \nCREATE TABLE student2(name CHAR(10),grade\nENUM(''b'',''c'',''a''));\n \nINSERT INTO student2\nVALUES(''Chun'',''b''),(''Esben'',''c''),(''Kaolin'',''a'');\n \nSELECT MAX(grade) FROM student2;\n \n+------------+\n| MAX(grade) |\n+------------+\n| c |\n+------------+\n \nSELECT grade FROM student2 ORDER BY grade DESC LIMIT 1;\n \n+-------+\n| grade |\n+-------+\n| a |\n+-------+\n \nAs a window function:\n \nCREATE OR REPLACE TABLE student_test (name CHAR(10), test\nCHAR(10), score TINYINT);\nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87);\n \nSELECT name, test, score, MAX(score) \n OVER (PARTITION BY name) AS highest_score FROM\nstudent_test;\n \n+---------+--------+-------+---------------+\n| name | test | score | highest_score |\n+---------+--------+-------+---------------+\n| Chun | SQL | 75 | 75 |\n| Chun | Tuning | 73 | 75 |\n| Esben | SQL | 43 | 43 |\n| Esben | Tuning | 31 | 43 |\n| Kaolin | SQL | 56 | 88 |\n| Kaolin | Tuning | 88 | 88 |\n| Tatiana | SQL | 87 | 87 |\n+---------+--------+-------+---------------+ -[MBRCONTAINS] +description=Returns the largest, or maximum, value of expr. MAX() can also take a string\nargument in which case it returns the maximum string value. The DISTINCT\nkeyword can be used to find the maximum of the distinct values of expr,\nhowever, this produces the same result as omitting DISTINCT.\n\nNote that SET and ENUM fields are currently compared by their string value\nrather than their relative position in the set, so MAX() may produce a\ndifferent highest result than ORDER BY DESC.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nMAX() can be used as a window function.\n\nMAX() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT);\n\nINSERT INTO student VALUES \n ('Chun', 'SQL', 75), ('Chun', 'Tuning', 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87), ('Tatiana', 'Tuning', 83);\n\nSELECT name, MAX(score) FROM student GROUP BY name;\n+---------+------------+\n| name | MAX(score) |\n+---------+------------+\n| Chun | 75 |\n| Esben | 43 |\n| Kaolin | 88 |\n| Tatiana | 87 |\n+---------+------------+\n\nMAX string:\n\nSELECT MAX(name) FROM student;\n+-----------+\n| MAX(name) |\n+-----------+\n| Tatiana |\n+-----------+\n\nBe careful to avoid this common mistake, not grouping correctly and returning\nmismatched data:\n\nSELECT name,test,MAX(SCORE) FROM student;\n+------+------+------------+\n ... +[MBRContains] declaration=g1,g2 category=MBR -description=Returns 1 or 0 to indicate whether the Minimum Bounding\nRectangle of\ng1 contains the Minimum Bounding Rectangle of g2. This tests\nthe\nopposite relationship as MBRWithin().\n \n\nSET @g1 = GeomFromText(''Polygon((0 0,0 3,3 3,3 0,0 0))'');\n \nSET @g2 = GeomFromText(''Point(1 1)'');\n \nSELECT MBRContains(@g1,@g2), MBRContains(@g2,@g1);\n+----------------------+----------------------+\n| MBRContains(@g1,@g2) | MBRContains(@g2,@g1) |\n+----------------------+----------------------+\n| 1 | 0 |\n+----------------------+----------------------+ -[MBRDISJOINT] +description=Returns 1 or 0 to indicate whether the Minimum Bounding Rectangle of g1\ncontains the Minimum Bounding Rectangle of g2. This tests the opposite\nrelationship as MBRWithin().\n\nExamples\n--------\n\nSET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))');\n\nSET @g2 = GeomFromText('Point(1 1)');\n\nSELECT MBRContains(@g1,@g2), MBRContains(@g2,@g1);\n+----------------------+----------------------+\n| MBRContains(@g1,@g2) | MBRContains(@g2,@g1) |\n+----------------------+----------------------+\n| 1 | 0 |\n+----------------------+----------------------+\n\nURL: https://mariadb.com/kb/en/mbrcontains/ +[MBRDisjoint] declaration=g1,g2 category=MBR -description=Returns 1 or 0 to indicate whether the Minimum Bounding\nRectangles of the two geometries g1 and g2 are disjoint. Two\ngeometries are disjoint if they do not intersect, that is\ntouch or overlap.\n \n\nSET @g1 = GeomFromText(''Polygon((0 0,0 3,3 3,3 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((4 4,4 7,7 7,7 4,4 4))'');\nSELECTmbrdisjoint(@g1,@g2);\n+----------------------+\n| mbrdisjoint(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n \nSET @g1 = GeomFromText(''Polygon((0 0,0 3,3 3,3 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((3 3,3 6,6 6,6 3,3 3))'');\nSELECT mbrdisjoint(@g1,@g2);\n+----------------------+\n| mbrdisjoint(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+ -[MBREQUAL] +description=Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 are disjoint. Two geometries are disjoint if they do not\nintersect, that is touch or overlap.\n\nExamples\n--------\n\nSET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))');\nSET @g2 = GeomFromText('Polygon((4 4,4 7,7 7,7 4,4 4))');\nSELECTmbrdisjoint(@g1,@g2);\n+----------------------+\n| mbrdisjoint(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))');\nSET @g2 = GeomFromText('Polygon((3 3,3 6,6 6,6 3,3 3))');\nSELECT mbrdisjoint(@g1,@g2);\n+----------------------+\n| mbrdisjoint(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/mbrdisjoint/ +[MBREqual] declaration=g1,g2 category=MBR -description=Returns 1 or 0 to indicate whether the Minimum Bounding\nRectangles of\nthe two geometries g1 and g2 are the same.\n \n\nSET @g1=GEOMFROMTEXT(''LINESTRING(0 0, 1 2)'');\nSET @g2=GEOMFROMTEXT(''POLYGON((0 0, 0 2, 1 2, 1 0, 0\n0))'');\nSELECT MbrEqual(@g1,@g2);\n+-------------------+\n| MbrEqual(@g1,@g2) |\n+-------------------+\n| 1 |\n+-------------------+\n \nSET @g1=GEOMFROMTEXT(''LINESTRING(0 0, 1 3)'');\nSET @g2=GEOMFROMTEXT(''POLYGON((0 0, 0 2, 1 4, 1 0, 0\n0))'');\nSELECT MbrEqual(@g1,@g2);\n+-------------------+\n| MbrEqual(@g1,@g2) |\n+-------------------+\n| 0 |\n+-------------------+ -[MBRINTERSECTS] +description=Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 are the same.\n\nExamples\n--------\n\nSET @g1=GEOMFROMTEXT('LINESTRING(0 0, 1 2)');\nSET @g2=GEOMFROMTEXT('POLYGON((0 0, 0 2, 1 2, 1 0, 0 0))');\nSELECT MbrEqual(@g1,@g2);\n+-------------------+\n| MbrEqual(@g1,@g2) |\n+-------------------+\n| 1 |\n+-------------------+\n\nSET @g1=GEOMFROMTEXT('LINESTRING(0 0, 1 3)');\nSET @g2=GEOMFROMTEXT('POLYGON((0 0, 0 2, 1 4, 1 0, 0 0))');\nSELECT MbrEqual(@g1,@g2);\n+-------------------+\n| MbrEqual(@g1,@g2) |\n+-------------------+\n| 0 |\n+-------------------+\n\nURL: https://mariadb.com/kb/en/mbrequal/ +[MBRIntersects] declaration=g1,g2 category=MBR -description=Returns 1 or 0 to indicate whether the Minimum Bounding\nRectangles of the two geometries g1 and g2 intersect.\n \n\nSET @g1 = GeomFromText(''Polygon((0 0,0 3,3 3,3 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((3 3,3 6,6 6,6 3,3 3))'');\nSELECT mbrintersects(@g1,@g2);\n+------------------------+\n| mbrintersects(@g1,@g2) |\n+------------------------+\n| 1 |\n+------------------------+\n \nSET @g1 = GeomFromText(''Polygon((0 0,0 3,3 3,3 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((4 4,4 7,7 7,7 4,4 4))'');\nSELECT mbrintersects(@g1,@g2);\n+------------------------+\n| mbrintersects(@g1,@g2) |\n+------------------------+\n| 0 |\n+------------------------+ -[MBROVERLAPS] +description=Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 intersect.\n\nExamples\n--------\n\nSET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))');\nSET @g2 = GeomFromText('Polygon((3 3,3 6,6 6,6 3,3 3))');\nSELECT mbrintersects(@g1,@g2);\n+------------------------+\n| mbrintersects(@g1,@g2) |\n+------------------------+\n| 1 |\n+------------------------+\n\nSET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))');\nSET @g2 = GeomFromText('Polygon((4 4,4 7,7 7,7 4,4 4))');\nSELECT mbrintersects(@g1,@g2);\n+------------------------+\n| mbrintersects(@g1,@g2) |\n+------------------------+\n| 0 |\n+------------------------+\n\nURL: https://mariadb.com/kb/en/mbrintersects/ +[MBROverlaps] declaration=g1,g2 category=MBR -description=Returns 1 or 0 to indicate whether the Minimum Bounding\nRectangles of\nthe two geometries g1 and g2 overlap. The term spatially\noverlaps is\nused if two geometries intersect and their intersection\nresults in a\ngeometry of the same dimension but not equal to either of\nthe given\ngeometries.\n \n\nSET @g1 = GeomFromText(''Polygon((0 0,0 3,3 3,3 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((4 4,4 7,7 7,7 4,4 4))'');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n \nSET @g1 = GeomFromText(''Polygon((0 0,0 3,3 3,3 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((3 3,3 6,6 6,6 3,3 3))'');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n \nSET @g1 = GeomFromText(''Polygon((0 0,0 4,4 4,4 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((3 3,3 6,6 6,6 3,3 3))'');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+ -[MBRTOUCHES] +description=Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 overlap. The term spatially overlaps is used if two\ngeometries intersect and their intersection results in a geometry of the same\ndimension but not equal to either of the given geometries.\n\nExamples\n--------\n\nSET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))');\nSET @g2 = GeomFromText('Polygon((4 4,4 7,7 7,7 4,4 4))');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nSET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))');\nSET @g2 = GeomFromText('Polygon((3 3,3 6,6 6,6 3,3 3))');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nSET @g1 = GeomFromText('Polygon((0 0,0 4,4 4,4 0,0 0))');\nSET @g2 = GeomFromText('Polygon((3 3,3 6,6 6,6 3,3 3))');\nSELECT mbroverlaps(@g1,@g2);\n+----------------------+\n| mbroverlaps(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/mbroverlaps/ +[MBRTouches] declaration=g1,g2 category=MBR -description=Returns 1 or 0 to indicate whether the Minimum Bounding\nRectangles of\nthe two geometries g1 and g2 touch. Two geometries spatially\ntouch if\nthe interiors of the geometries do not intersect, but the\nboundary of\none of the geometries intersects either the boundary or the\ninterior\nof the other.\n \n\nSET @g1 = GeomFromText(''Polygon((0 0,0 3,3 3,3 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((4 4,4 7,7 7,7 4,4 4))'');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+\n \nSET @g1 = GeomFromText(''Polygon((0 0,0 3,3 3,3 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((3 3,3 6,6 6,6 3,3 3))'');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 1 |\n+---------------------+\n \nSET @g1 = GeomFromText(''Polygon((0 0,0 4,4 4,4 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((3 3,3 6,6 6,6 3,3 3))'');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+ -[MBRWITHIN] +description=Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles of the two\ngeometries g1 and g2 touch. Two geometries spatially touch if the interiors of\nthe geometries do not intersect, but the boundary of one of the geometries\nintersects either the boundary or the interior of the other.\n\nExamples\n--------\n\nSET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))');\nSET @g2 = GeomFromText('Polygon((4 4,4 7,7 7,7 4,4 4))');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+\n\nSET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))');\nSET @g2 = GeomFromText('Polygon((3 3,3 6,6 6,6 3,3 3))');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 1 |\n+---------------------+\n\nSET @g1 = GeomFromText('Polygon((0 0,0 4,4 4,4 0,0 0))');\nSET @g2 = GeomFromText('Polygon((3 3,3 6,6 6,6 3,3 3))');\nSELECT mbrtouches(@g1,@g2);\n+---------------------+\n| mbrtouches(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/mbrtouches/ +[MBRWithin] declaration=g1,g2 category=MBR -description=Returns 1 or 0 to indicate whether the Minimum Bounding\nRectangle of\ng1 is within the Minimum Bounding Rectangle of g2. This\ntests the\nopposite relationship as MBRContains().\n \n\nSET @g1 = GeomFromText(''Polygon((0 0,0 3,3 3,3 0,0 0))'');\nSET @g2 = GeomFromText(''Polygon((0 0,0 5,5 5,5 0,0 0))'');\nSELECT MBRWithin(@g1,@g2), MBRWithin(@g2,@g1);\n+--------------------+--------------------+\n| MBRWithin(@g1,@g2) | MBRWithin(@g2,@g1) |\n+--------------------+--------------------+\n| 1 | 0 |\n+--------------------+--------------------+ +description=Returns 1 or 0 to indicate whether the Minimum Bounding Rectangle of g1 is\nwithin the Minimum Bounding Rectangle of g2. This tests the opposite\nrelationship as MBRContains().\n\nExamples\n--------\n\nSET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))');\nSET @g2 = GeomFromText('Polygon((0 0,0 5,5 5,5 0,0 0))');\nSELECT MBRWithin(@g1,@g2), MBRWithin(@g2,@g1);\n+--------------------+--------------------+\n| MBRWithin(@g1,@g2) | MBRWithin(@g2,@g1) |\n+--------------------+--------------------+\n| 1 | 0 |\n+--------------------+--------------------+\n\nURL: https://mariadb.com/kb/en/mbrwithin/ [MD5] declaration=str category=Encryption Functions -description=Calculates an MD5 128-bit checksum for the string. \n \nThe return value is a 32-hex digit string, and as of MariaDB\n5.5, is a nonbinary string in the connection character set\nand collation, determined by the values of the\ncharacter_set_connection and collation_connection system\nvariables. Before 5.5, the return value was a binary string.\n \nNULL is returned if the argument was NULL. \n \n\nSELECT MD5(''testing'');\n+----------------------------------+\n| MD5(''testing'') |\n+----------------------------------+\n| ae2b1fca515949e5d54fb22b8ed95575 |\n+----------------------------------+ -[MEDIAN1] -name=MEDIAN -declaration=median expression -category=Window Functions -description=MEDIAN() is a window function that returns the median value\nof a range of values.\n \nIt is a specific case of PERCENTILE_CONT, with an argument\nof 0.5 and the ORDER BY column the one in MEDIAN''s\nargument. \n \nMEDIAN() OVER ( [ PARTITION BY partition_expression] )\n \nIs equivalent to:\n \nPERCENTILE_CONT(0.5) WITHIN \n GROUP (ORDER BY ) OVER ( [ PARTITION BY\npartition_expression ])\n \n\nCREATE TABLE book_rating (name CHAR(30), star_rating\nTINYINT);\n \nINSERT INTO book_rating VALUES (''Lord of the Ladybirds'',\n5);\nINSERT INTO book_rating VALUES (''Lord of the Ladybirds'',\n3);\nINSERT INTO book_rating VALUES (''Lady of the Flies'', 1);\nINSERT INTO book_rating VALUES (''Lady of the Flies'', 2);\nINSERT INTO book_rating VALUES (''Lady of the Flies'', 5);\n \nSELECT name, median(star_rating) OVER (PARTITION BY name)\nFROM book_rating;\n \n+-----------------------+----------------------------------------------+\n| name | median(star_rating) OVER (PARTITION BY name) |\n+-----------------------+----------------------------------------------+\n| Lord of the Ladybirds | 4.0000000000 |\n| Lord of the Ladybirds | 4.0000000000 |\n| Lady of the Flies | 2.0000000000 |\n| Lady of the Flies | 2.0000000000 |\n| Lady of the Flies | 2.0000000000 |\n+-----------------------+----------------------------------------------+ -[MEDIAN2] -name=MEDIAN -declaration=[ PARTITION BY partition_expression ] -category=Window Functions -description=MEDIAN() is a window function that returns the median value\nof a range of values.\n \nIt is a specific case of PERCENTILE_CONT, with an argument\nof 0.5 and the ORDER BY column the one in MEDIAN''s\nargument. \n \nMEDIAN() OVER ( [ PARTITION BY partition_expression] )\n \nIs equivalent to:\n \nPERCENTILE_CONT(0.5) WITHIN \n GROUP (ORDER BY ) OVER ( [ PARTITION BY\npartition_expression ])\n \n\nCREATE TABLE book_rating (name CHAR(30), star_rating\nTINYINT);\n \nINSERT INTO book_rating VALUES (''Lord of the Ladybirds'',\n5);\nINSERT INTO book_rating VALUES (''Lord of the Ladybirds'',\n3);\nINSERT INTO book_rating VALUES (''Lady of the Flies'', 1);\nINSERT INTO book_rating VALUES (''Lady of the Flies'', 2);\nINSERT INTO book_rating VALUES (''Lady of the Flies'', 5);\n \nSELECT name, median(star_rating) OVER (PARTITION BY name)\nFROM book_rating;\n \n+-----------------------+----------------------------------------------+\n| name | median(star_rating) OVER (PARTITION BY name) |\n+-----------------------+----------------------------------------------+\n| Lord of the Ladybirds | 4.0000000000 |\n| Lord of the Ladybirds | 4.0000000000 |\n| Lady of the Flies | 2.0000000000 |\n| Lady of the Flies | 2.0000000000 |\n| Lady of the Flies | 2.0000000000 |\n+-----------------------+----------------------------------------------+ +description=Calculates an MD5 128-bit checksum for the string.\n\nThe return value is a 32-hex digit string, and as of MariaDB 5.5, is a\nnonbinary string in the connection character set and collation, determined by\nthe values of the character_set_connection and collation_connection system\nvariables. Before 5.5, the return value was a binary string.\n\nNULL is returned if the argument was NULL.\n\nExamples\n--------\n\nSELECT MD5('testing');\n+----------------------------------+\n| MD5('testing') |\n+----------------------------------+\n| ae2b1fca515949e5d54fb22b8ed95575 |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/md5/ +[MEDIUMINT] +declaration=M +category=Data Types +description=A medium-sized integer. The signed range is -8388608 to 8388607. The unsigned\nrange is 0 to 16777215.\n\nZEROFILL pads the integer with zeroes and assumes UNSIGNED (even if UNSIGNED\nis not specified).\n\nINT3 is a synonym for MEDIUMINT.\n\nFor details on the attributes, see Numeric Data Type Overview.\n\nExamples\n--------\n\nCREATE TABLE mediumints (a MEDIUMINT,b MEDIUMINT UNSIGNED,c MEDIUMINT\nZEROFILL);\n\nDESCRIBE mediumints;\n+-------+--------------------------------+------+-----+---------+-------+\n| Field | Type | Null | Key | Default | Extra |\n+-------+--------------------------------+------+-----+---------+-------+\n| a | mediumint(9) | YES | | NULL | |\n| b | mediumint(8) unsigned | YES | | NULL | |\n| c | mediumint(8) unsigned zerofill | YES | | NULL | |\n+-------+--------------------------------+------+-----+---------+-------+\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO mediumints VALUES (-10,-10,-10);\nERROR 1264 (22003): Out of range value for column 'b' at row 1\n\nINSERT INTO mediumints VALUES (-10,10,-10);\nERROR 1264 (22003): Out of range value for column 'c' at row 1\n\nINSERT INTO mediumints VALUES (-10,10,10);\n\nINSERT INTO mediumints VALUES (8388608,8388608,8388608);\nERROR 1264 (22003): Out of range value for column 'a' at row 1\n\nINSERT INTO mediumints VALUES (8388607,8388608,8388608);\n\nSELECT * FROM mediumints;\n+---------+---------+----------+\n| a | b | c |\n+---------+---------+----------+\n| -10 | 10 | 00000010 |\n| 8388607 | 8388608 | 08388608 |\n+---------+---------+----------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\n ... [MICROSECOND] declaration=expr category=Date and Time Functions -description=Returns the microseconds from the time or datetime\nexpression expr as a number in the range from 0 to 999999.\n \nIf expr is a time with no microseconds, zero is returned,\nwhile if expr is a date with no time, zero with a warning is\nreturned.\n \n\nSELECT MICROSECOND(''12:00:00.123456'');\n+--------------------------------+\n| MICROSECOND(''12:00:00.123456'') |\n+--------------------------------+\n| 123456 |\n+--------------------------------+\n \nSELECT MICROSECOND(''2009-12-31 23:59:59.000010'');\n+-------------------------------------------+\n| MICROSECOND(''2009-12-31 23:59:59.000010'') |\n+-------------------------------------------+\n| 10 |\n+-------------------------------------------+\n \nSELECT MICROSECOND(''2013-08-07 12:13:14'');\n+------------------------------------+\n| MICROSECOND(''2013-08-07 12:13:14'') |\n+------------------------------------+\n| 0 |\n+------------------------------------+\n \nSELECT MICROSECOND(''2013-08-07'');\n+---------------------------+\n| MICROSECOND(''2013-08-07'') |\n+---------------------------+\n| 0 |\n+---------------------------+\n1 row in set, 1 warning (0.00 sec)\n \nSHOW WARNINGS;\n \n+---------+------+----------------------------------------------+\n| Level | Code | Message |\n+---------+------+----------------------------------------------+\n| Warning | 1292 | Truncated incorrect time value:\n''2013-08-07'' |\n+---------+------+----------------------------------------------+ +description=Returns the microseconds from the time or datetime expression expr as a number\nin the range from 0 to 999999.\n\nIf expr is a time with no microseconds, zero is returned, while if expr is a\ndate with no time, zero with a warning is returned.\n\nExamples\n--------\n\nSELECT MICROSECOND('12:00:00.123456');\n+--------------------------------+\n| MICROSECOND('12:00:00.123456') |\n+--------------------------------+\n| 123456 |\n+--------------------------------+\n\nSELECT MICROSECOND('2009-12-31 23:59:59.000010');\n+-------------------------------------------+\n| MICROSECOND('2009-12-31 23:59:59.000010') |\n+-------------------------------------------+\n| 10 |\n+-------------------------------------------+\n\nSELECT MICROSECOND('2013-08-07 12:13:14');\n+------------------------------------+\n| MICROSECOND('2013-08-07 12:13:14') |\n+------------------------------------+\n| 0 |\n+------------------------------------+\n\nSELECT MICROSECOND('2013-08-07');\n+---------------------------+\n| MICROSECOND('2013-08-07') |\n+---------------------------+\n| 0 |\n+---------------------------+\n1 row in set, 1 warning (0.00 sec)\n\nSHOW WARNINGS;\n+---------+------+----------------------------------------------+\n| Level | Code | Message |\n+---------+------+----------------------------------------------+\n| Warning | 1292 | Truncated incorrect time value: '2013-08-07' |\n+---------+------+----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/microsecond/ [MID] declaration=str,pos,len category=String Functions -description=MID(str,pos,len) is a synonym for SUBSTRING(str,pos,len).\n \n\nSELECT MID(''abcd'',4,1);\n+-----------------+\n| MID(''abcd'',4,1) |\n+-----------------+\n| d |\n+-----------------+\n \nSELECT MID(''abcd'',2,2);\n+-----------------+\n| MID(''abcd'',2,2) |\n+-----------------+\n| bc |\n+-----------------+\n \nA negative starting position:\n \nSELECT MID(''abcd'',-2,4);\n+------------------+\n| MID(''abcd'',-2,4) |\n+------------------+\n| cd |\n+------------------+ -[MINUTE] -declaration=time -category=Date and Time Functions -description=Returns the minute for time, in the range 0 to 59. \n \n\nSELECT MINUTE(''2013-08-03 11:04:03'');\n+-------------------------------+\n| MINUTE(''2013-08-03 11:04:03'') |\n+-------------------------------+\n| 4 |\n+-------------------------------+\n \n SELECT MINUTE (''23:12:50'');\n+---------------------+\n| MINUTE (''23:12:50'') |\n+---------------------+\n| 12 |\n+---------------------+ +description=MID(str,pos,len) is a synonym for SUBSTRING(str,pos,len).\n\nExamples\n--------\n\nSELECT MID('abcd',4,1);\n+-----------------+\n| MID('abcd',4,1) |\n+-----------------+\n| d |\n+-----------------+\n\nSELECT MID('abcd',2,2);\n+-----------------+\n| MID('abcd',2,2) |\n+-----------------+\n| bc |\n+-----------------+\n\nA negative starting position:\n\nSELECT MID('abcd',-2,4);\n+------------------+\n| MID('abcd',-2,4) |\n+------------------+\n| cd |\n+------------------+\n\nURL: https://mariadb.com/kb/en/mid/ [MIN] declaration=[DISTINCT] expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the minimum value of expr. MIN() may take a string\nargument, in which case it returns the minimum string value.\nThe DISTINCT\nkeyword can be used to find the minimum of the distinct\nvalues of expr,\nhowever, this produces the same result as omitting DISTINCT.\n \nNote that SET and ENUM fields are currently compared by\ntheir string value rather than their relative position in\nthe set, so MIN() may produce a different lowest result than\nORDER BY ASC.\n \nIt is an aggregate function, and so can be used with the\nGROUP BY clause.\n \nFrom MariaDB 10.2.2, MIN() can be used as a window function.\n \nMIN() returns NULL if there were no matching rows.\n \n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score\nTINYINT); \n \nINSERT INTO student VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87), (''Tatiana'', ''Tuning'', 83);\n \nSELECT name, MIN(score) FROM student GROUP BY name;\n \n+---------+------------+\n| name | MIN(score) |\n+---------+------------+\n| Chun | 73 |\n| Esben | 31 |\n| Kaolin | 56 |\n| Tatiana | 83 |\n+---------+------------+\n \nMIN() with a string:\n \nSELECT MIN(name) FROM student;\n \n+-----------+\n| MIN(name) |\n+-----------+\n| Chun |\n+-----------+\n \nBe careful to avoid this common mistake, not grouping\ncorrectly and returning mismatched data: \n \nSELECT name,test,MIN(score) FROM student;\n \n+------+------+------------+\n| name | test | MIN(score) |\n+------+------+------------+\n| Chun | SQL | 31 |\n+------+------+------------+\n \nDifference between ORDER BY ASC and MIN():\n \nCREATE TABLE student2(name CHAR(10),grade\nENUM(''b'',''c'',''a''));\n \nINSERT INTO student2\nVALUES(''Chun'',''b''),(''Esben'',''c''),(''Kaolin'',''a'');\n \nSELECT MIN(grade) FROM student2;\n \n+------------+\n| MIN(grade) |\n+------------+\n| a |\n+------------+\n \nSELECT grade FROM student2 ORDER BY grade ASC LIMIT 1;\n \n+-------+\n| grade |\n+-------+\n| b |\n+-------+\n \nAs a window function:\n \nCREATE OR REPLACE TABLE student_test (name CHAR(10), test\nCHAR(10), score TINYINT);\nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87);\n \nSELECT name, test, score, MIN(score) \n OVER (PARTITION BY name) AS lowest_score FROM student_test;\n \n+---------+--------+-------+--------------+\n| name | test | score | lowest_score |\n+---------+--------+-------+--------------+\n| Chun | SQL | 75 | 73 |\n| Chun | Tuning | 73 | 73 |\n| Esben | SQL | 43 | 31 |\n| Esben | Tuning | 31 | 31 |\n| Kaolin | SQL | 56 | 56 |\n| Kaolin | Tuning | 88 | 56 |\n| Tatiana | SQL | 87 | 87 |\n+---------+--------+-------+--------------+ -[MLINEFROMTEXT] +description=Returns the minimum value of expr. MIN() may take a string argument, in which\ncase it returns the minimum string value. The DISTINCT keyword can be used to\nfind the minimum of the distinct values of expr, however, this produces the\nsame result as omitting DISTINCT.\n\nNote that SET and ENUM fields are currently compared by their string value\nrather than their relative position in the set, so MIN() may produce a\ndifferent lowest result than ORDER BY ASC.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nMIN() can be used as a window function.\n\nMIN() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nCREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT);\n\nINSERT INTO student VALUES \n ('Chun', 'SQL', 75), ('Chun', 'Tuning', 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87), ('Tatiana', 'Tuning', 83);\n\nSELECT name, MIN(score) FROM student GROUP BY name;\n+---------+------------+\n| name | MIN(score) |\n+---------+------------+\n| Chun | 73 |\n| Esben | 31 |\n| Kaolin | 56 |\n| Tatiana | 83 |\n+---------+------------+\n\nMIN() with a string:\n\nSELECT MIN(name) FROM student;\n+-----------+\n| MIN(name) |\n+-----------+\n| Chun |\n+-----------+\n\nBe careful to avoid this common mistake, not grouping correctly and returning\nmismatched data:\n\nSELECT name,test,MIN(score) FROM student;\n+------+------+------------+\n ... +[MINUTE] +declaration=time +category=Date and Time Functions +description=Returns the minute for time, in the range 0 to 59.\n\nExamples\n--------\n\nSELECT MINUTE('2013-08-03 11:04:03');\n+-------------------------------+\n| MINUTE('2013-08-03 11:04:03') |\n+-------------------------------+\n| 4 |\n+-------------------------------+\n\nSELECT MINUTE ('23:12:50');\n+---------------------+\n| MINUTE ('23:12:50') |\n+---------------------+\n| 12 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/minute/ +[MLineFromText] declaration=wkt[,srid] category=WKT -description=Constructs a MULTILINESTRING value using its WKT\nrepresentation and SRID.\n \nMLineFromText() and MultiLineStringFromText() are synonyms.\n \n\nCREATE TABLE gis_multi_line (g MULTILINESTRING);\nSHOW FIELDS FROM gis_multi_line;\n \nINSERT INTO gis_multi_line VALUES\n (MultiLineStringFromText(''MULTILINESTRING((10 48,10 21,10\n0),(16 0,16 23,16 48))'')),\n (MLineFromText(''MULTILINESTRING((10 48,10 21,10 0))'')),\n (MLineFromWKB(AsWKB(MultiLineString(LineString(Point(1, 2),\nPoint(3, 5)), LineString(Point(2, 5), Point(5, 8), Point(21,\n7)))))); -[MLINEFROMWKB] +description=Constructs a MULTILINESTRING value using its WKT representation and SRID.\n\nMLineFromText() and MultiLineStringFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_multi_line (g MULTILINESTRING);\nSHOW FIELDS FROM gis_multi_line;\nINSERT INTO gis_multi_line VALUES\n (MultiLineStringFromText('MULTILINESTRING((10 48,10 21,10 0),(16 0,16\n23,16 48))')),\n (MLineFromText('MULTILINESTRING((10 48,10 21,10 0))')),\n (MLineFromWKB(AsWKB(MultiLineString(\n LineString(Point(1, 2), Point(3, 5)),\n LineString(Point(2, 5), Point(5, 8), Point(21, 7))))));\n\nURL: https://mariadb.com/kb/en/mlinefromtext/ +[MLineFromWKB] declaration=wkb[,srid] category=WKB -description=Constructs a MULTILINESTRING value using its WKB\nrepresentation and SRID.\n \nMLineFromWKB() and MultiLineStringFromWKB() are synonyms.\n \n\nSET @g = ST_AsBinary(MLineFromText(''MULTILINESTRING((10\n48,10 21,10 0),(16 0,16 23,16 48))''));\n \nSELECT ST_AsText(MLineFromWKB(@g));\n+--------------------------------------------------------+\n| ST_AsText(MLineFromWKB(@g)) |\n+--------------------------------------------------------+\n| MULTILINESTRING((10 48,10 21,10 0),(16 0,16 23,16 48)) |\n+--------------------------------------------------------+ +description=Constructs a MULTILINESTRING value using its WKB representation and SRID.\n\nMLineFromWKB() and MultiLineStringFromWKB() are synonyms.\n\nExamples\n--------\n\nSET @g = ST_AsBinary(MLineFromText('MULTILINESTRING((10 48,10 21,10 0),(16\n0,16 23,16 48))'));\n\nSELECT ST_AsText(MLineFromWKB(@g));\n+--------------------------------------------------------+\n| ST_AsText(MLineFromWKB(@g)) |\n+--------------------------------------------------------+\n| MULTILINESTRING((10 48,10 21,10 0),(16 0,16 23,16 48)) |\n+--------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/mlinefromwkb/ [MOD] declaration=N,M category=Numeric Functions -description=Modulo operation. Returns the remainder of N divided by M.\nSee also Modulo Operator.\n \nIf the ERROR_ON_DIVISION_BY_ZERO SQL_MODE is used, any\nnumber modulus zero produces an error. Otherwise, it returns\nNULL.\n \nThe integer part of a division can be obtained using DIV.\n \n\nSELECT 1042 % 50;\n \n+-----------+\n| 1042 % 50 |\n+-----------+\n| 42 |\n+-----------+\n \nSELECT MOD(234, 10);\n+--------------+\n| MOD(234, 10) |\n+--------------+\n| 4 |\n+--------------+\n \nSELECT 253 % 7;\n \n+---------+\n| 253 % 7 |\n+---------+\n| 1 |\n+---------+\n \nSELECT MOD(29,9);\n+-----------+\n| MOD(29,9) |\n+-----------+\n| 2 |\n+-----------+\n \nSELECT 29 MOD 9;\n \n+----------+\n| 29 MOD 9 |\n+----------+\n| 2 |\n+----------+ -[MONTHNAME] +description=Modulo operation. Returns the remainder of N divided by M. See also Modulo\nOperator.\n\nIf the ERROR_ON_DIVISION_BY_ZERO SQL_MODE is used, any number modulus zero\nproduces an error. Otherwise, it returns NULL.\n\nThe integer part of a division can be obtained using DIV.\n\nExamples\n--------\n\nSELECT 1042 % 50;\n+-----------+\n| 1042 % 50 |\n+-----------+\n| 42 |\n+-----------+\n\nSELECT MOD(234, 10);\n+--------------+\n| MOD(234, 10) |\n+--------------+\n| 4 |\n+--------------+\n\nSELECT 253 % 7;\n+---------+\n| 253 % 7 |\n+---------+\n| 1 |\n+---------+\n\nSELECT MOD(29,9);\n+-----------+\n| MOD(29,9) |\n+-----------+\n| 2 |\n+-----------+\n\nSELECT 29 MOD 9;\n+----------+\n| 29 MOD 9 |\n+----------+\n| 2 |\n+----------+\n\nURL: https://mariadb.com/kb/en/mod/ +[MONTH] declaration=date category=Date and Time Functions -description=Returns the full name of the month for date. The language\nused for the name is controlled by the value of the\nlc_time_names system variable. See server locale for more on\nthe supported locales.\n \n\nSELECT MONTHNAME(''2019-02-03'');\n+-------------------------+\n| MONTHNAME(''2019-02-03'') |\n+-------------------------+\n| February |\n+-------------------------+\n \nChanging the locale:\n \nSET lc_time_names = ''fr_CA'';\n \nSELECT MONTHNAME(''2019-05-21'');\n+-------------------------+\n| MONTHNAME(''2019-05-21'') |\n+-------------------------+\n| mai |\n+-------------------------+ -[MONTH] +description=Returns the month for date in the range 1 to 12 for January to December, or 0\nfor dates such as '0000-00-00' or '2008-00-00' that have a zero month part.\n\nExamples\n--------\n\nSELECT MONTH('2019-01-03');\n+---------------------+\n| MONTH('2019-01-03') |\n+---------------------+\n| 1 |\n+---------------------+\n\nSELECT MONTH('2019-00-03');\n+---------------------+\n| MONTH('2019-00-03') |\n+---------------------+\n| 0 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/month/ +[MONTHNAME] declaration=date category=Date and Time Functions -description=Returns the month for date in the range 1 to 12 for January\nto\nDecember, or 0 for dates such as ''0000-00-00'' or\n''2008-00-00'' that\nhave a zero month part.\n \n\nSELECT MONTH(''2019-01-03'');\n+---------------------+\n| MONTH(''2019-01-03'') |\n+---------------------+\n| 1 |\n+---------------------+\n \nSELECT MONTH(''2019-00-03'');\n+---------------------+\n| MONTH(''2019-00-03'') |\n+---------------------+\n| 0 |\n+---------------------+ -[MPOINTFROMTEXT] +description=Returns the full name of the month for date. The language used for the name is\ncontrolled by the value of the lc_time_names system variable. See server\nlocale for more on the supported locales.\n\nExamples\n--------\n\nSELECT MONTHNAME('2019-02-03');\n+-------------------------+\n| MONTHNAME('2019-02-03') |\n+-------------------------+\n| February |\n+-------------------------+\n\nChanging the locale:\n\nSET lc_time_names = 'fr_CA';\n\nSELECT MONTHNAME('2019-05-21');\n+-------------------------+\n| MONTHNAME('2019-05-21') |\n+-------------------------+\n| mai |\n+-------------------------+\n\nURL: https://mariadb.com/kb/en/monthname/ +[MPointFromText] declaration=wkt[,srid] category=WKT -description=Constructs a MULTIPOINT value using its WKT representation\nand SRID.\n \nMPointFromText() and MultiPointFromText() are synonyms.\n \n\nCREATE TABLE gis_multi_point (g MULTIPOINT);\nSHOW FIELDS FROM gis_multi_point;\n \nINSERT INTO gis_multi_point VALUES\n (MultiPointFromText(''MULTIPOINT(0 0,10 10,10 20,20\n20)'')),\n (MPointFromText(''MULTIPOINT(1 1,11 11,11 21,21 21)'')),\n (MPointFromWKB(AsWKB(MultiPoint(Point(3, 6), Point(4,\n10))))); -[MPOINTFROMWKB] +description=Constructs a MULTIPOINT value using its WKT representation and SRID.\n\nMPointFromText() and MultiPointFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_multi_point (g MULTIPOINT);\nSHOW FIELDS FROM gis_multi_point;\nINSERT INTO gis_multi_point VALUES\n (MultiPointFromText('MULTIPOINT(0 0,10 10,10 20,20 20)')),\n (MPointFromText('MULTIPOINT(1 1,11 11,11 21,21 21)')),\n (MPointFromWKB(AsWKB(MultiPoint(Point(3, 6), Point(4, 10)))));\n\nURL: https://mariadb.com/kb/en/mpointfromtext/ +[MPointFromWKB] declaration=wkb[,srid] category=WKB -description=Constructs a MULTIPOINT value using its WKB representation\nand SRID.\n \nMPointFromWKB() and MultiPointFromWKB() are synonyms.\n \n\nSET @g = ST_AsBinary(MPointFromText(''MultiPoint( 1 1, 2 2,\n5 3, 7 2, 9 3, 8 4, 6 6, 6 9, 4 9, 1 5 )''));\n \nSELECT ST_AsText(MPointFromWKB(@g));\n+-----------------------------------------------------+\n| ST_AsText(MPointFromWKB(@g)) |\n+-----------------------------------------------------+\n| MULTIPOINT(1 1,2 2,5 3,7 2,9 3,8 4,6 6,6 9,4 9,1 5) |\n+-----------------------------------------------------+ -[MPOLYFROMTEXT] +description=Constructs a MULTIPOINT value using its WKB representation and SRID.\n\nMPointFromWKB() and MultiPointFromWKB() are synonyms.\n\nExamples\n--------\n\nSET @g = ST_AsBinary(MPointFromText('MultiPoint( 1 1, 2 2, 5 3, 7 2, 9 3, 8 4,\n6 6, 6 9, 4 9, 1 5 )'));\n\nSELECT ST_AsText(MPointFromWKB(@g));\n+-----------------------------------------------------+\n| ST_AsText(MPointFromWKB(@g)) |\n+-----------------------------------------------------+\n| MULTIPOINT(1 1,2 2,5 3,7 2,9 3,8 4,6 6,6 9,4 9,1 5) |\n+-----------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/mpointfromwkb/ +[MPolyFromText] declaration=wkt[,srid] category=WKT -description=Constructs a MULTIPOLYGON value using its WKT representation\nand SRID.\n \nMPolyFromText() and MultiPolygonFromText() are synonyms.\n \n\nCREATE TABLE gis_multi_polygon (g MULTIPOLYGON);\nSHOW FIELDS FROM gis_multi_polygon;\n \nINSERT INTO gis_multi_polygon VALUES\n (MultiPolygonFromText(''MULTIPOLYGON(((28 26,28 0,84 0,84\n42,28 26),(52 18,66 23,73 9,48 6,52 18)),((59 18,67 18,67\n13,59 13,59 18)))'')),\n (MPolyFromText(''MULTIPOLYGON(((28 26,28 0,84 0,84 42,28\n26),(52 18,66 23,73 9,48 6,52 18)),((59 18,67 18,67 13,59\n13,59 18)))'')),\n (MPolyFromWKB(AsWKB(MultiPolygon(Polygon(LineString(Point(0,\n3), Point(3, 3), Point(3, 0), Point(0, 3))))))); -[MPOLYFROMWKB] +description=Constructs a MULTIPOLYGON value using its WKT representation and SRID.\n\nMPolyFromText() and MultiPolygonFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_multi_polygon (g MULTIPOLYGON);\nSHOW FIELDS FROM gis_multi_polygon;\nINSERT INTO gis_multi_polygon VALUES\n (MultiPolygonFromText('MULTIPOLYGON(\n ((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)),\n ((59 18,67 18,67 13,59 13,59 18)))')),\n (MPolyFromText('MULTIPOLYGON(\n ((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)),\n ((59 18,67 18,67 13,59 13,59 18)))')),\n (MPolyFromWKB(AsWKB(MultiPolygon(Polygon(\n LineString(Point(0, 3), Point(3, 3), Point(3, 0), Point(0, 3)))))));\n\nURL: https://mariadb.com/kb/en/mpolyfromtext/ +[MPolyFromWKB] declaration=wkb[,srid] category=WKB -description=Constructs a MULTIPOLYGON value using its WKB representation\nand SRID.\n \nMPolyFromWKB() and MultiPolygonFromWKB() are synonyms.\n \n\nSET @g = ST_AsBinary(MPointFromText(''MULTIPOLYGON(((28\n26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52\n18)),((59 18,67 18,67 13,59 13,59 18)))''));\n \nSELECT ST_AsText(MPolyFromWKB(@g));\n+---------------------------------------------------------------------------------------------------------------+\n| ST_AsText(MPolyFromWKB(@g)) |\n+---------------------------------------------------------------------------------------------------------------+\n| MULTIPOLYGON(((28 26,28 0,84 0,84 42,28 26),(52 18,66\n23,73 9,48 6,52 18)),((59 18,67 18,67 13,59 13,59 18))) |\n+---------------------------------------------------------------------------------------------------------------+ +description=Constructs a MULTIPOLYGON value using its WKB representation and SRID.\n\nMPolyFromWKB() and MultiPolygonFromWKB() are synonyms.\n\nExamples\n--------\n\nSET @g = ST_AsBinary(MPointFromText('MULTIPOLYGON(((28 26,28 0,84 0,84 42,28\n26),(52 18,66 23,73 9,48 6,52 18)),((59 18,67 18,67 13,59 13,59 18)))'));\n\nSELECT ST_AsText(MPolyFromWKB(@g))\G\n*************************** 1. row ***************************\nST_AsText(MPolyFromWKB(@g)): MULTIPOLYGON(((28 26,28 0,84 0,84 42,28 26),(52\n18,66 23,73 9,48 6,52 18)),((59 18,67 18,67 13,59 13,59 18)))\n\nURL: https://mariadb.com/kb/en/mpolyfromwkb/ [MULTILINESTRING] declaration=ls1,ls2,... category=Geometry Constructors -description=Constructs a WKB MultiLineString value using WKB LineString\narguments. If any argument is not a WKB LineString, the\nreturn value is\nNULL.\n \nExample\n \nCREATE TABLE gis_multi_line (g MULTILINESTRING);\nINSERT INTO gis_multi_line VALUES\n (MultiLineStringFromText(''MULTILINESTRING((10 48,10 21,10\n0),(16 0,16 23,16 48))'')),\n (MLineFromText(''MULTILINESTRING((10 48,10 21,10 0))'')),\n (MLineFromWKB(AsWKB(MultiLineString(LineString(Point(1, 2),\nPoint(3, 5)), LineString(Point(2, 5),Point(5, 8),Point(21,\n7)))))); +description=Constructs a WKB MultiLineString value using WKB LineString arguments. If any\nargument is not a WKB LineString, the return value is NULL.\n\nExample\n-------\n\nCREATE TABLE gis_multi_line (g MULTILINESTRING);\nINSERT INTO gis_multi_line VALUES\n (MultiLineStringFromText('MULTILINESTRING((10 48,10 21,10 0),(16 0,16 23,16\n48))')),\n (MLineFromText('MULTILINESTRING((10 48,10 21,10 0))')),\n (MLineFromWKB(AsWKB(MultiLineString(LineString(Point(1, 2), \n Point(3, 5)), LineString(Point(2, 5),Point(5, 8),Point(21, 7))))));\n\nURL: https://mariadb.com/kb/en/multilinestring/ [MULTIPOINT] declaration=pt1,pt2,... category=Geometry Constructors -description=Constructs a WKB MultiPoint value using WKB Point arguments.\nIf any argument is not a WKB Point, the return value is\nNULL.\n \n\nSET @g = ST_GEOMFROMTEXT(''MultiPoint( 1 1, 2 2, 5 3, 7 2, 9\n3, 8 4, 6 6, 6 9, 4 9, 1 5 )'');\n \nCREATE TABLE gis_multi_point (g MULTIPOINT);\nINSERT INTO gis_multi_point VALUES\n (MultiPointFromText(''MULTIPOINT(0 0,10 10,10 20,20\n20)'')),\n (MPointFromText(''MULTIPOINT(1 1,11 11,11 21,21 21)'')),\n (MPointFromWKB(AsWKB(MultiPoint(Point(3, 6), Point(4,\n10))))); +description=Constructs a WKB MultiPoint value using WKB Point arguments. If any argument\nis not a WKB Point, the return value is NULL.\n\nExamples\n--------\n\nSET @g = ST_GEOMFROMTEXT('MultiPoint( 1 1, 2 2, 5 3, 7 2, 9 3, 8 4, 6 6, 6 9,\n4 9, 1 5 )');\n\nCREATE TABLE gis_multi_point (g MULTIPOINT);\nINSERT INTO gis_multi_point VALUES\n (MultiPointFromText('MULTIPOINT(0 0,10 10,10 20,20 20)')),\n (MPointFromText('MULTIPOINT(1 1,11 11,11 21,21 21)')),\n (MPointFromWKB(AsWKB(MultiPoint(Point(3, 6), Point(4, 10)))));\n\nURL: https://mariadb.com/kb/en/multipoint/ [MULTIPOLYGON] declaration=poly1,poly2,... category=Geometry Constructors -description=Constructs a WKB MultiPolygon value from a set of WKB\nPolygon arguments. If any argument is not a WKB Polygon, the\nreturn value is NULL.\n \nExample\n \nCREATE TABLE gis_multi_polygon (g MULTIPOLYGON);\nINSERT INTO gis_multi_polygon VALUES\n (MultiPolygonFromText(''MULTIPOLYGON(((28 26,28 0,84 0,84\n42,28 26),(52 18,66 23,73 9,48 6,52 18)),((59 18,67 18,67\n13,59 13,59 18)))'')),\n (MPolyFromText(''MULTIPOLYGON(((28 26,28 0,84 0,84 42,28\n26),(52 18,66 23,73 9,48 6,52 18)),((59 18,67 18,67 13,59\n13,59 18)))'')),\n (MPolyFromWKB(AsWKB(MultiPolygon(Polygon(LineString(Point(0,\n3), Point(3, 3), Point(3, 0), Point(0, 3))))))); +description=Constructs a WKB MultiPolygon value from a set of WKB Polygon arguments. If\nany argument is not a WKB Polygon, the return value is NULL.\n\nExample\n-------\n\nCREATE TABLE gis_multi_polygon (g MULTIPOLYGON);\nINSERT INTO gis_multi_polygon VALUES\n (MultiPolygonFromText('MULTIPOLYGON(((28 26,28 0,84 0,84 42,28 26),(52\n18,66 23,73 9,48 6,52 18)),\n ((59 18,67 18,67 13,59 13,59 18)))')),\n (MPolyFromText('MULTIPOLYGON(((28 26,28 0,84 0,84 42,28 26),(52 18,66\n23,73 9,48 6,52 18)),\n ((59 18,67 18,67 13,59 13,59 18)))')),\n (MPolyFromWKB(AsWKB(MultiPolygon(Polygon(LineString(\n Point(0, 3), Point(3, 3), Point(3, 0), Point(0, 3)))))));\n\nURL: https://mariadb.com/kb/en/multipolygon/ [NAME_CONST] declaration=name,value category=Miscellaneous Functions -description=Returns the given value. When used to produce a result set\ncolumn,\n NAME_CONST() causes the column to have the given name. The\narguments should be constants.\n \nThis function is used internally when replicating stored\nprocedures. It makes little sense to use it explicitly in\nSQL statements, and it was not supposed to be used like\nthat.\n \nSELECT NAME_CONST(''myname'', 14);\n+--------+\n| myname |\n+--------+\n| 14 |\n+--------+ +description=Returns the given value. When used to produce a result set column,\nNAME_CONST() causes the column to have the given name. The arguments should be\nconstants.\n\nThis function is used internally when replicating stored procedures. It makes\nlittle sense to use it explicitly in SQL statements, and it was not supposed\nto be used like that.\n\nSELECT NAME_CONST('myname', 14);\n+--------+\n| myname |\n+--------+\n| 14 |\n+--------+\n\nURL: https://mariadb.com/kb/en/name_const/ +[NATURAL_SORT_KEY] +declaration=str +category=String Functions +description=The NATURAL_SORT_KEY function is used for sorting that is closer to natural\nsorting. Strings are sorted in alphabetical order, while numbers are treated\nin a way such that, for example, 10 is greater than 2, whereas in other forms\nof sorting, 2 would be greater than 10, just like z is greater than ya.\n\nThere are multiple natural sort implementations, differing in the way they\nhandle leading zeroes, fractions, i18n, negatives, decimals and so on.\n\nMariaDB's implementation ignores leading zeroes when performing the sort.\n\nYou can use also use NATURAL_SORT_KEY with generated columns. The value is not\nstored permanently in the table. When using a generated column, the virtual\ncolumn must be longer than the base column to cater for embedded numbers in\nthe string and MDEV-24582.\n\nExamples\n--------\n\nStrings and Numbers\n-------------------\n\nCREATE TABLE t1 (c TEXT);\n\nINSERT INTO t1 VALUES ('b1'),('a2'),('a11'),('a1');\n\nSELECT c FROM t1;\n+------+\n| c |\n+------+\n| b1 |\n| a2 |\n| a11 |\n| a1 |\n+------+\n\nSELECT c FROM t1 ORDER BY c;\n+------+\n| c |\n+------+\n| a1 |\n| a11 |\n| a2 |\n| b1 |\n+------+\n\nUnsorted, regular sort and natural sort:\n\nTRUNCATE t1;\n\nINSERT INTO t1 VALUES \n ... [NOW] declaration=[precision] category=Date and Time Functions -description=Returns the current date and time as a value in ''YYYY-MM-DD\nHH:MM:SS''\nor YYYYMMDDHHMMSS.uuuuuu format, depending on whether the\nfunction is\nused in a string or numeric context. The value is expressed\nin the\ncurrent time zone.\n \nThe optional precision determines the microsecond precision.\nSee Microseconds in MariaDB.\n \nNOW() (or its synonyms) can be used as the default value for\nTIMESTAMP columns as well as, since MariaDB 10.0.1, DATETIME\ncolumns. Before MariaDB 10.0.1, it was only possible for a\nsingle TIMESTAMP column per table to contain the\nCURRENT_TIMESTAMP as its default.\n \nWhen displayed in the INFORMATION_SCHEMA.COLUMNS table, a\ndefault CURRENT TIMESTAMP is displayed as CURRENT_TIMESTAMP\nup until MariaDB 10.2.2, and as current_timestamp() from\nMariaDB 10.2.3, due to to MariaDB 10.2 accepting expressions\nin the DEFAULT clause.\n \n\nSELECT NOW();\n+---------------------+\n| NOW() |\n+---------------------+\n| 2010-03-27 13:13:25 |\n+---------------------+\n \nSELECT NOW() + 0;\n \n+-----------------------+\n| NOW() + 0 |\n+-----------------------+\n| 20100327131329.000000 |\n+-----------------------+\n \nWith precision:\n \nSELECT CURRENT_TIMESTAMP(2);\n+------------------------+\n| CURRENT_TIMESTAMP(2) |\n+------------------------+\n| 2018-07-10 09:47:26.24 |\n+------------------------+\n \nUsed as a default TIMESTAMP:\n \nCREATE TABLE t (createdTS TIMESTAMP NOT NULL DEFAULT\nCURRENT_TIMESTAMP);\n \nFrom MariaDB 10.2.2:\n \nSELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE\nTABLE_SCHEMA=''test''\n AND COLUMN_NAME LIKE ''%ts%''\G\n*************************** 1. row\n***************************\n TABLE_CATALOG: def\n TABLE_SCHEMA: test\n TABLE_NAME: t\n COLUMN_NAME: ts\n ORDINAL_POSITION: 1\n COLUMN_DEFAULT: current_timestamp()\n... +description=Returns the current date and time as a value in 'YYYY-MM-DD HH:MM:SS' or\nYYYYMMDDHHMMSS.uuuuuu format, depending on whether the function is used in a\nstring or numeric context. The value is expressed in the current time zone.\n\nThe optional precision determines the microsecond precision. See Microseconds\nin MariaDB.\n\nNOW() (or its synonyms) can be used as the default value for TIMESTAMP columns\nas well as, since MariaDB 10.0.1, DATETIME columns. Before MariaDB 10.0.1, it\nwas only possible for a single TIMESTAMP column per table to contain the\nCURRENT_TIMESTAMP as its default.\n\nWhen displayed in the INFORMATION_SCHEMA.COLUMNS table, a default CURRENT\nTIMESTAMP is displayed as CURRENT_TIMESTAMP up until MariaDB 10.2.2, and as\ncurrent_timestamp() from MariaDB 10.2.3, due to to MariaDB 10.2 accepting\nexpressions in the DEFAULT clause.\n\nChanging the timestamp system variable with a SET timestamp statement affects\nthe value returned by NOW(), but not by SYSDATE().\n\nExamples\n--------\n\nSELECT NOW();\n+---------------------+\n| NOW() |\n+---------------------+\n| 2010-03-27 13:13:25 |\n+---------------------+\n\nSELECT NOW() + 0;\n+-----------------------+\n| NOW() + 0 |\n+-----------------------+\n| 20100327131329.000000 |\n+-----------------------+\n\nWith precision:\n\nSELECT CURRENT_TIMESTAMP(2);\n+------------------------+\n| CURRENT_TIMESTAMP(2) |\n+------------------------+\n| 2018-07-10 09:47:26.24 |\n+------------------------+\n\nUsed as a default TIMESTAMP:\n\nCREATE TABLE t (createdTS TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);\n\n ... +[NTH_VALUE] +declaration=expr[, num_row] +category=Window Functions +description=The NTH_VALUE function returns the value evaluated at row number num_row of\nthe window frame, starting from 1, or NULL if the row does not exist.\n\nURL: https://mariadb.com/kb/en/nth_value/ +[NTILE] +declaration=expr +category=Window Functions +description=NTILE() is a window function that returns an integer indicating which group a\ngiven row falls into. The number of groups is specified in the argument\n(expr), starting at one. Ordered rows in the partition are divided into the\nspecified number of groups with as equal a size as possible.\n\nExamples\n--------\n\ncreate table t1 (\n pk int primary key,\n a int,\n b int\n );\n\ninsert into t1 values\n (11 , 0, 10),\n (12 , 0, 10),\n (13 , 1, 10),\n (14 , 1, 10),\n (18 , 2, 10),\n (15 , 2, 20),\n (16 , 2, 20),\n (17 , 2, 20),\n (19 , 4, 20),\n (20 , 4, 20);\n\nselect pk, a, b,\n ntile(1) over (order by pk)\n from t1;\n+----+------+------+-----------------------------+\n| pk | a | b | ntile(1) over (order by pk) |\n+----+------+------+-----------------------------+\n| 11 | 0 | 10 | 1 |\n| 12 | 0 | 10 | 1 |\n| 13 | 1 | 10 | 1 |\n| 14 | 1 | 10 | 1 |\n| 15 | 2 | 20 | 1 |\n| 16 | 2 | 20 | 1 |\n| 17 | 2 | 20 | 1 |\n| 18 | 2 | 10 | 1 |\n| 19 | 4 | 20 | 1 |\n| 20 | 4 | 20 | 1 |\n+----+------+------+-----------------------------+\n\nselect pk, a, b,\n ntile(4) over (order by pk)\n from t1;\n+----+------+------+-----------------------------+\n| pk | a | b | ntile(4) over (order by pk) |\n+----+------+------+-----------------------------+\n ... [NULLIF] declaration=expr1,expr2 category=Control Flow Functions -description=Returns NULL if expr1 = expr2 is true, otherwise returns\nexpr1. This is\nthe same as CASE WHEN expr1 = expr2 THEN NULL ELSE expr1\nEND.\n \n\nSELECT NULLIF(1,1);\n+-------------+\n| NULLIF(1,1) |\n+-------------+\n| NULL |\n+-------------+\n \nSELECT NULLIF(1,2);\n+-------------+\n| NULLIF(1,2) |\n+-------------+\n| 1 |\n+-------------+ -[OCTET_LENGTH] -declaration=str -category=String Functions -description=OCTET_LENGTH() is normally a synonym for LENGTH(). When\nrunning Oracle mode from MariaDB 10.3, they are not\nsynonyms, but OCTET_LENGTH() behaves as LENGTH() would when\nnot in Oracle mode. +description=Returns NULL if expr1 = expr2 is true, otherwise returns expr1. This is the\nsame as CASE WHEN expr1 = expr2 THEN NULL ELSE expr1 END.\n\nExamples\n--------\n\nSELECT NULLIF(1,1);\n+-------------+\n| NULLIF(1,1) |\n+-------------+\n| NULL |\n+-------------+\n\nSELECT NULLIF(1,2);\n+-------------+\n| NULLIF(1,2) |\n+-------------+\n| 1 |\n+-------------+\n\nURL: https://mariadb.com/kb/en/nullif/ +[NVL2] +declaration=expr1,expr2,expr3 +category=Control Flow Functions +description=The NVL2 function returns a value based on whether a specified expression is\nNULL or not. If expr1 is not NULL, then NVL2 returns expr2. If expr1 is NULL,\nthen NVL2 returns expr3.\n\nExamples\n--------\n\nSELECT NVL2(NULL,1,2);\n+----------------+\n| NVL2(NULL,1,2) |\n+----------------+\n| 2 |\n+----------------+\n\nSELECT NVL2('x',1,2);\n+---------------+\n| NVL2('x',1,2) |\n+---------------+\n| 1 |\n+---------------+\n\nURL: https://mariadb.com/kb/en/nvl2/ [OCT] declaration=N category=Numeric Functions -description=Returns a string representation of the octal value of N,\nwhere N is a longlong (BIGINT) number. This is equivalent to\nCONV(N,10,8). Returns NULL if N is NULL.\n \n\nSELECT OCT(34);\n+---------+\n| OCT(34) |\n+---------+\n| 42 |\n+---------+\n \nSELECT OCT(12);\n+---------+\n| OCT(12) |\n+---------+\n| 14 |\n+---------+ +description=Returns a string representation of the octal value of N, where N is a longlong\n(BIGINT) number. This is equivalent to CONV(N,10,8). Returns NULL if N is NULL.\n\nExamples\n--------\n\nSELECT OCT(34);\n+---------+\n| OCT(34) |\n+---------+\n| 42 |\n+---------+\n\nSELECT OCT(12);\n+---------+\n| OCT(12) |\n+---------+\n| 14 |\n+---------+\n\nURL: https://mariadb.com/kb/en/oct/ +[OCTET_LENGTH] +declaration=str +category=String Functions +description=OCTET_LENGTH() returns the length of the given string, in octets (bytes). This\nis a synonym for LENGTHB(), and, when Oracle mode from MariaDB 10.3 is not\nset, a synonym for LENGTH().\n\nA multi-byte character counts as multiple bytes. This means that for a string\ncontaining five two-byte characters, OCTET_LENGTH() returns 10, whereas\nCHAR_LENGTH() returns 5.\n\nIf str is not a string value, it is converted into a string. If str is NULL,\nthe function returns NULL.\n\nExamples\n--------\n\nWhen Oracle mode from MariaDB 10.3 is not set:\n\nSELECT CHAR_LENGTH('π'), LENGTH('π'), LENGTHB('π'), OCTET_LENGTH('π');\n+-------------------+--------------+---------------+--------------------+\n| CHAR_LENGTH('π') | LENGTH('π') | LENGTHB('π') | OCTET_LENGTH('π') |\n+-------------------+--------------+---------------+--------------------+\n| 1 | 2 | 2 | 2 |\n+-------------------+--------------+---------------+--------------------+\n\nIn Oracle mode from MariaDB 10.3:\n\nSELECT CHAR_LENGTH('π'), LENGTH('π'), LENGTHB('π'), OCTET_LENGTH('π');\n+-------------------+--------------+---------------+--------------------+\n| CHAR_LENGTH('π') | LENGTH('π') | LENGTHB('π') | OCTET_LENGTH('π') |\n+-------------------+--------------+---------------+--------------------+\n| 1 | 1 | 2 | 2 |\n+-------------------+--------------+---------------+--------------------+\n\nURL: https://mariadb.com/kb/en/octet_length/ [OLD_PASSWORD] declaration=str category=Encryption Functions -description=OLD_PASSWORD() was added to MySQL when the implementation of\n\nPASSWORD() was changed to improve security. OLD_PASSWORD()\nreturns the\nvalue of the old (pre-MySQL 4.1) implementation of\nPASSWORD() as a\nstring, and is intended to permit you to reset passwords for\nany\npre-4.1 clients that need to connect to a more recent MySQL\nserver version, or any version of MariaDB,\nwithout locking them out.\n \nAs of MariaDB 5.5, the return value is a nonbinary string in\nthe connection character set and collation, determined by\nthe values of the character_set_connection and\ncollation_connection system variables. Before 5.5, the\nreturn value was a binary string.\n \nThe return value is 16 bytes in length, or NULL if the\nargument was NULL. +description=OLD_PASSWORD() was added to MySQL when the implementation of PASSWORD() was\nchanged to improve security. OLD_PASSWORD() returns the value of the old\n(pre-MySQL 4.1) implementation of PASSWORD() as a string, and is intended to\npermit you to reset passwords for any pre-4.1 clients that need to connect to\na more recent MySQL server version, or any version of MariaDB, without locking\nthem out.\n\nAs of MariaDB 5.5, the return value is a nonbinary string in the connection\ncharacter set and collation, determined by the values of the\ncharacter_set_connection and collation_connection system variables. Before\n5.5, the return value was a binary string.\n\nThe return value is 16 bytes in length, or NULL if the argument was NULL.\n\nURL: https://mariadb.com/kb/en/old_password/ [ORD] declaration=str category=String Functions -description=If the leftmost character of the string str is a multi-byte\ncharacter,\nreturns the code for that character, calculated from the\nnumeric\nvalues of its constituent bytes using this formula:\n \n (1st byte code)\n+ (2nd byte code x 256)\n+ (3rd byte code x 256 x 256) ...\n \nIf the leftmost character is not a multi-byte character,\nORD() returns\nthe same value as the ASCII() function.\n \n\nSELECT ORD(''2'');\n+----------+\n| ORD(''2'') |\n+----------+\n| 50 |\n+----------+ +description=If the leftmost character of the string str is a multi-byte character, returns\nthe code for that character, calculated from the numeric values of its\nconstituent bytes using this formula:\n\n(1st byte code)\n+ (2nd byte code x 256)\n+ (3rd byte code x 256 x 256) ...\n\nIf the leftmost character is not a multi-byte character, ORD() returns the\nsame value as the ASCII() function.\n\nExamples\n--------\n\nSELECT ORD('2');\n+----------+\n| ORD('2') |\n+----------+\n| 50 |\n+----------+\n\nURL: https://mariadb.com/kb/en/ord/ [OVERLAPS] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether g1 spatially overlaps g2.\nThe term spatially overlaps is used if two geometries\nintersect and their\nintersection results in a geometry of the same dimension but\nnot equal to\neither of the given geometries.\n \nOVERLAPS() is based on the original MySQL implementation and\nuses object bounding rectangles, while ST_OVERLAPS() uses\nobject shapes. +description=Returns 1 or 0 to indicate whether g1 spatially overlaps g2. The term\nspatially overlaps is used if two geometries intersect and their intersection\nresults in a geometry of the same dimension but not equal to either of the\ngiven geometries.\n\nOVERLAPS() is based on the original MySQL implementation and uses object\nbounding rectangles, while ST_OVERLAPS() uses object shapes.\n\nURL: https://mariadb.com/kb/en/overlaps/ [PASSWORD] declaration=str category=Encryption Functions -description=The PASSWORD() function is used for hashing passwords for\nuse in authentication by the MariaDB server. It is not\nintended for use in other applications.\n \nCalculates and returns a hashed password string from the\nplaintext password str. Returns an empty string (>= MariaDB\n10.0.4) or NULL ( -[PERCENT_RANK1] -name=PERCENT_RANK +description=The PASSWORD() function is used for hashing passwords for use in\nauthentication by the MariaDB server. It is not intended for use in other\napplications.\n\nCalculates and returns a hashed password string from the plaintext password\nstr. Returns an empty string (>= MariaDB 10.0.4) if the argument was NULL.\n\nThe return value is a nonbinary string in the connection character set and\ncollation, determined by the values of the character_set_connection and\ncollation_connection system variables.\n\nThis is the function that is used for hashing MariaDB passwords for storage in\nthe Password column of the user table (see privileges), usually used with the\nSET PASSWORD statement. It is not intended for use in other applications.\n\nUntil MariaDB 10.3, the return value is 41-bytes in length, and the first\ncharacter is always '*'. From MariaDB 10.4, the function takes into account\nthe authentication plugin where applicable (A CREATE USER or SET PASSWORD\nstatement). For example, when used in conjunction with a user authenticated by\nthe ed25519 plugin, the statement will create a longer hash:\n\nCREATE USER edtest@localhost IDENTIFIED VIA ed25519 USING PASSWORD('secret');\n\nCREATE USER edtest2@localhost IDENTIFIED BY 'secret';\n\nSELECT CONCAT(user, '@', host, ' => ', JSON_DETAILED(priv)) FROM\nmysql.global_priv\n WHERE user LIKE 'edtest%'\G\n*************************** 1. row ***************************\nCONCAT(user, '@', host, ' => ', JSON_DETAILED(priv)): edtest@localhost => {\n...\n "plugin": "ed25519",\n "authentication_string": "ZIgUREUg5PVgQ6LskhXmO+eZLS0nC8be6HPjYWR4YJY",\n...\n}\n*************************** 2. row ***************************\nCONCAT(user, '@', host, ' => ', JSON_DETAILED(priv)): edtest2@localhost => {\n...\n "plugin": "mysql_native_password",\n "authentication_string": "*14E65567ABDB5135D0CFD9A70B3032C179A49EE7",\n...\n}\n\nThe behavior of this function is affected by the value of the old_passwords\nsystem variable. If this is set to 1 (0 is default), MariaDB reverts to using\nthe mysql_old_password authentication plugin by default for newly created\nusers and passwords.\n\nExamples\n--------\n ... +[PERCENTILE_CONT] declaration= category=Window Functions -description=PERCENT_RANK() is a window function that returns the\nrelative percent rank of a given row. The following formula\nis used to calculate the percent rank:\n \n(rank - 1) / (number of rows in the window or partition - 1)\n \n\ncreate table t1 (\n pk int primary key,\n a int,\n b int\n);\n \ninsert into t1 values\n( 1 , 0, 10),\n( 2 , 0, 10),\n( 3 , 1, 10),\n( 4 , 1, 10),\n( 8 , 2, 10),\n( 5 , 2, 20),\n( 6 , 2, 20),\n( 7 , 2, 20),\n( 9 , 4, 20),\n(10 , 4, 20);\n \nselect pk, a, b,\n rank() over (order by a) as rank,\n percent_rank() over (order by a) as pct_rank,\n cume_dist() over (order by a) as cume_dist\nfrom t1;\n \n+----+------+------+------+--------------+--------------+\n| pk | a | b | rank | pct_rank | cume_dist |\n+----+------+------+------+--------------+--------------+\n| 1 | 0 | 10 | 1 | 0.0000000000 | 0.2000000000 |\n| 2 | 0 | 10 | 1 | 0.0000000000 | 0.2000000000 |\n| 3 | 1 | 10 | 3 | 0.2222222222 | 0.4000000000 |\n| 4 | 1 | 10 | 3 | 0.2222222222 | 0.4000000000 |\n| 5 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 6 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 7 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 8 | 2 | 10 | 5 | 0.4444444444 | 0.8000000000 |\n| 9 | 4 | 20 | 9 | 0.8888888889 | 1.0000000000 |\n| 10 | 4 | 20 | 9 | 0.8888888889 | 1.0000000000 |\n+----+------+------+------+--------------+--------------+\n \nselect pk, a, b,\n percent_rank() over (order by pk) as pct_rank,\n cume_dist() over (order by pk) as cume_dist\nfrom t1 order by pk;\n \n+----+------+------+--------------+--------------+\n| pk | a | b | pct_rank | cume_dist |\n+----+------+------+--------------+--------------+\n| 1 | 0 | 10 | 0.0000000000 | 0.1000000000 |\n| 2 | 0 | 10 | 0.1111111111 | 0.2000000000 |\n| 3 | 1 | 10 | 0.2222222222 | 0.3000000000 |\n| 4 | 1 | 10 | 0.3333333333 | 0.4000000000 |\n| 5 | 2 | 20 | 0.4444444444 | 0.5000000000 |\n| 6 | 2 | 20 | 0.5555555556 | 0.6000000000 |\n| 7 | 2 | 20 | 0.6666666667 | 0.7000000000 |\n| 8 | 2 | 10 | 0.7777777778 | 0.8000000000 |\n| 9 | 4 | 20 | 0.8888888889 | 0.9000000000 |\n| 10 | 4 | 20 | 1.0000000000 | 1.0000000000 |\n+----+------+------+--------------+--------------+\n \nselect pk, a, b,\n percent_rank() over (partition by a order by a) as\npct_rank,\n cume_dist() over (partition by a order by a) as cume_dist\nfrom t1;\n \n+----+------+------+--------------+--------------+\n| pk | a | b | pct_rank | cume_dist |\n+----+------+------+--------------+--------------+\n| 1 | 0 | 10 | 0.0000000000 | 1.0000000000 |\n| 2 | 0 | 10 | 0.0000000000 | 1.0000000000 |\n| 3 | 1 | 10 | 0.0000000000 | 1.0000000000 |\n| 4 | 1 | 10 | 0.0000000000 | 1.0000000000 |\n| 5 | 2 | 20 | 0.0000000000 | 1.0000000000 |\n| 6 | 2 | 20 | 0.0000000000 | 1.0000000000 |\n| 7 | 2 | 20 | 0.0000000000 | 1.0000000000 |\n| 8 | 2 | 10 | 0.0000000000 | 1.0000000000 |\n| 9 | 4 | 20 | 0.0000000000 | 1.0000000000 |\n| 10 | 4 | 20 | 0.0000000000 | 1.0000000000 |\n+----+------+------+--------------+--------------+ -[PERCENT_RANK2] -name=PERCENT_RANK -declaration=[ PARTITION BY partition_expression ] [ ORDER BY order_list ] +description=PERCENTILE_CONT() (standing for continuous percentile) is a window function\nwhich returns a value which corresponds to the given fraction in the sort\norder. If required, it will interpolate between adjacent input items.\n\nEssentially, the following process is followed to find the value to return:\n\n* Get the number of rows in the partition, denoted by N\n* RN = p*(N-1), where p denotes the argument to the PERCENTILE_CONT function\n* calculate the FRN(floor row number) and CRN(column row number for the group(\nFRN= floor(RN) and CRN = ceil(RN))\n* look up rows FRN and CRN\n* If (CRN = FRN = RN) then the result is (value of expression from row at RN)\n* Otherwise the result is\n* (CRN - RN) * (value of expression for row at FRN) +\n* (RN - FRN) * (value of expression for row at CRN)\n\nThe MEDIAN function is a specific case of PERCENTILE_CONT, equivalent to\nPERCENTILE_CONT(0.5).\n\nExamples\n--------\n\nCREATE TABLE book_rating (name CHAR(30), star_rating TINYINT);\n\nINSERT INTO book_rating VALUES ('Lord of the Ladybirds', 5);\nINSERT INTO book_rating VALUES ('Lord of the Ladybirds', 3);\nINSERT INTO book_rating VALUES ('Lady of the Flies', 1);\nINSERT INTO book_rating VALUES ('Lady of the Flies', 2);\nINSERT INTO book_rating VALUES ('Lady of the Flies', 5);\n\nSELECT name, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY star_rating) \n OVER (PARTITION BY name) AS pc\n FROM book_rating;\n+-----------------------+--------------+\n| name | pc |\n+-----------------------+--------------+\n| Lord of the Ladybirds | 4.0000000000 |\n| Lord of the Ladybirds | 4.0000000000 |\n| Lady of the Flies | 2.0000000000 |\n| Lady of the Flies | 2.0000000000 |\n| Lady of the Flies | 2.0000000000 |\n+-----------------------+--------------+\n\nSELECT name, PERCENTILE_CONT(1) WITHIN GROUP (ORDER BY star_rating) \n OVER (PARTITION BY name) AS pc\n FROM book_rating;\n+-----------------------+--------------+\n| name | pc |\n+-----------------------+--------------+\n| Lord of the Ladybirds | 5.0000000000 |\n ... +[PERCENTILE_DISC] +declaration= +category=Window Functions +description=PERCENTILE_DISC() (standing for discrete percentile) is a window function\nwhich returns the first value in the set whose ordered position is the same or\nmore than the specified fraction.\n\nEssentially, the following process is followed to find the value to return:\n\n* Get the number of rows in the partition.\n* Walk through the partition, in order, until finding the the first row with\nCUME_DIST() >= function_argument.\n\nExamples\n--------\n\nCREATE TABLE book_rating (name CHAR(30), star_rating TINYINT);\n\nINSERT INTO book_rating VALUES ('Lord of the Ladybirds', 5);\nINSERT INTO book_rating VALUES ('Lord of the Ladybirds', 3);\nINSERT INTO book_rating VALUES ('Lady of the Flies', 1);\nINSERT INTO book_rating VALUES ('Lady of the Flies', 2);\nINSERT INTO book_rating VALUES ('Lady of the Flies', 5);\n\nSELECT name, PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY star_rating)\n OVER (PARTITION BY name) AS pc FROM book_rating;\n+-----------------------+------+\n| name | pc |\n+-----------------------+------+\n| Lord of the Ladybirds | 3 |\n| Lord of the Ladybirds | 3 |\n| Lady of the Flies | 2 |\n| Lady of the Flies | 2 |\n| Lady of the Flies | 2 |\n+-----------------------+------+\n5 rows in set (0.000 sec)\n\nSELECT name, PERCENTILE_DISC(0) WITHIN GROUP (ORDER BY star_rating) \n OVER (PARTITION BY name) AS pc FROM book_rating;\n+-----------------------+------+\n| name | pc |\n+-----------------------+------+\n| Lord of the Ladybirds | 3 |\n| Lord of the Ladybirds | 3 |\n| Lady of the Flies | 1 |\n| Lady of the Flies | 1 |\n| Lady of the Flies | 1 |\n+-----------------------+------+\n5 rows in set (0.000 sec)\n\nSELECT name, PERCENTILE_DISC(1) WITHIN GROUP (ORDER BY star_rating) \n OVER (PARTITION BY name) AS pc FROM book_rating;\n+-----------------------+------+\n ... +[PERCENT_RANK] +declaration= category=Window Functions -description=PERCENT_RANK() is a window function that returns the\nrelative percent rank of a given row. The following formula\nis used to calculate the percent rank:\n \n(rank - 1) / (number of rows in the window or partition - 1)\n \n\ncreate table t1 (\n pk int primary key,\n a int,\n b int\n);\n \ninsert into t1 values\n( 1 , 0, 10),\n( 2 , 0, 10),\n( 3 , 1, 10),\n( 4 , 1, 10),\n( 8 , 2, 10),\n( 5 , 2, 20),\n( 6 , 2, 20),\n( 7 , 2, 20),\n( 9 , 4, 20),\n(10 , 4, 20);\n \nselect pk, a, b,\n rank() over (order by a) as rank,\n percent_rank() over (order by a) as pct_rank,\n cume_dist() over (order by a) as cume_dist\nfrom t1;\n \n+----+------+------+------+--------------+--------------+\n| pk | a | b | rank | pct_rank | cume_dist |\n+----+------+------+------+--------------+--------------+\n| 1 | 0 | 10 | 1 | 0.0000000000 | 0.2000000000 |\n| 2 | 0 | 10 | 1 | 0.0000000000 | 0.2000000000 |\n| 3 | 1 | 10 | 3 | 0.2222222222 | 0.4000000000 |\n| 4 | 1 | 10 | 3 | 0.2222222222 | 0.4000000000 |\n| 5 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 6 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 7 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 8 | 2 | 10 | 5 | 0.4444444444 | 0.8000000000 |\n| 9 | 4 | 20 | 9 | 0.8888888889 | 1.0000000000 |\n| 10 | 4 | 20 | 9 | 0.8888888889 | 1.0000000000 |\n+----+------+------+------+--------------+--------------+\n \nselect pk, a, b,\n percent_rank() over (order by pk) as pct_rank,\n cume_dist() over (order by pk) as cume_dist\nfrom t1 order by pk;\n \n+----+------+------+--------------+--------------+\n| pk | a | b | pct_rank | cume_dist |\n+----+------+------+--------------+--------------+\n| 1 | 0 | 10 | 0.0000000000 | 0.1000000000 |\n| 2 | 0 | 10 | 0.1111111111 | 0.2000000000 |\n| 3 | 1 | 10 | 0.2222222222 | 0.3000000000 |\n| 4 | 1 | 10 | 0.3333333333 | 0.4000000000 |\n| 5 | 2 | 20 | 0.4444444444 | 0.5000000000 |\n| 6 | 2 | 20 | 0.5555555556 | 0.6000000000 |\n| 7 | 2 | 20 | 0.6666666667 | 0.7000000000 |\n| 8 | 2 | 10 | 0.7777777778 | 0.8000000000 |\n| 9 | 4 | 20 | 0.8888888889 | 0.9000000000 |\n| 10 | 4 | 20 | 1.0000000000 | 1.0000000000 |\n+----+------+------+--------------+--------------+\n \nselect pk, a, b,\n percent_rank() over (partition by a order by a) as\npct_rank,\n cume_dist() over (partition by a order by a) as cume_dist\nfrom t1;\n \n+----+------+------+--------------+--------------+\n| pk | a | b | pct_rank | cume_dist |\n+----+------+------+--------------+--------------+\n| 1 | 0 | 10 | 0.0000000000 | 1.0000000000 |\n| 2 | 0 | 10 | 0.0000000000 | 1.0000000000 |\n| 3 | 1 | 10 | 0.0000000000 | 1.0000000000 |\n| 4 | 1 | 10 | 0.0000000000 | 1.0000000000 |\n| 5 | 2 | 20 | 0.0000000000 | 1.0000000000 |\n| 6 | 2 | 20 | 0.0000000000 | 1.0000000000 |\n| 7 | 2 | 20 | 0.0000000000 | 1.0000000000 |\n| 8 | 2 | 10 | 0.0000000000 | 1.0000000000 |\n| 9 | 4 | 20 | 0.0000000000 | 1.0000000000 |\n| 10 | 4 | 20 | 0.0000000000 | 1.0000000000 |\n+----+------+------+--------------+--------------+ +description=PERCENT_RANK() is a window function that returns the relative percent rank of\na given row. The following formula is used to calculate the percent rank:\n\n(rank - 1) / (number of rows in the window or partition - 1)\n\nExamples\n--------\n\ncreate table t1 (\n pk int primary key,\n a int,\n b int\n);\n\ninsert into t1 values\n( 1 , 0, 10),\n( 2 , 0, 10),\n( 3 , 1, 10),\n( 4 , 1, 10),\n( 8 , 2, 10),\n( 5 , 2, 20),\n( 6 , 2, 20),\n( 7 , 2, 20),\n( 9 , 4, 20),\n(10 , 4, 20);\n\nselect pk, a, b,\n rank() over (order by a) as rank,\n percent_rank() over (order by a) as pct_rank,\n cume_dist() over (order by a) as cume_dist\nfrom t1;\n+----+------+------+------+--------------+--------------+\n| pk | a | b | rank | pct_rank | cume_dist |\n+----+------+------+------+--------------+--------------+\n| 1 | 0 | 10 | 1 | 0.0000000000 | 0.2000000000 |\n| 2 | 0 | 10 | 1 | 0.0000000000 | 0.2000000000 |\n| 3 | 1 | 10 | 3 | 0.2222222222 | 0.4000000000 |\n| 4 | 1 | 10 | 3 | 0.2222222222 | 0.4000000000 |\n| 5 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 6 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 7 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 |\n| 8 | 2 | 10 | 5 | 0.4444444444 | 0.8000000000 |\n| 9 | 4 | 20 | 9 | 0.8888888889 | 1.0000000000 |\n| 10 | 4 | 20 | 9 | 0.8888888889 | 1.0000000000 |\n+----+------+------+------+--------------+--------------+\n\nselect pk, a, b,\n percent_rank() over (order by pk) as pct_rank,\n cume_dist() over (order by pk) as cume_dist\nfrom t1 order by pk;\n ... [PERIOD_ADD] declaration=P,N category=Date and Time Functions -description=Adds N months to period P. P is in the format YYMM or\nYYYYMM, and is not a date value. If P contains a two-digit\nyear, values from 00 to 69 are converted to from 2000 to\n2069, while values from 70 are converted to 1970 upwards.\n \nReturns a value in the format YYYYMM.\n \n\nSELECT PERIOD_ADD(200801,2);\n+----------------------+\n| PERIOD_ADD(200801,2) |\n+----------------------+\n| 200803 |\n+----------------------+\n \nSELECT PERIOD_ADD(6910,2);\n+--------------------+\n| PERIOD_ADD(6910,2) |\n+--------------------+\n| 206912 |\n+--------------------+\n \nSELECT PERIOD_ADD(7010,2);\n+--------------------+\n| PERIOD_ADD(7010,2) |\n+--------------------+\n| 197012 |\n+--------------------+ +description=Adds N months to period P. P is in the format YYMM or YYYYMM, and is not a\ndate value. If P contains a two-digit year, values from 00 to 69 are converted\nto from 2000 to 2069, while values from 70 are converted to 1970 upwards.\n\nReturns a value in the format YYYYMM.\n\nExamples\n--------\n\nSELECT PERIOD_ADD(200801,2);\n+----------------------+\n| PERIOD_ADD(200801,2) |\n+----------------------+\n| 200803 |\n+----------------------+\n\nSELECT PERIOD_ADD(6910,2);\n+--------------------+\n| PERIOD_ADD(6910,2) |\n+--------------------+\n| 206912 |\n+--------------------+\n\nSELECT PERIOD_ADD(7010,2);\n+--------------------+\n| PERIOD_ADD(7010,2) |\n+--------------------+\n| 197012 |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/period_add/ [PERIOD_DIFF] declaration=P1,P2 category=Date and Time Functions -description=Returns the number of months between periods P1 and P2. P1\nand P2 \ncan be in the format YYMM or YYYYMM, and are not date\nvalues.\n \nIf P1 or P2 contains a two-digit year, values from 00 to 69\nare converted to from 2000 to 2069, while values from 70 are\nconverted to 1970 upwards.\n \n\nSELECT PERIOD_DIFF(200802,200703);\n+----------------------------+\n| PERIOD_DIFF(200802,200703) |\n+----------------------------+\n| 11 |\n+----------------------------+\n \nSELECT PERIOD_DIFF(6902,6803);\n+------------------------+\n| PERIOD_DIFF(6902,6803) |\n+------------------------+\n| 11 |\n+------------------------+\n \nSELECT PERIOD_DIFF(7002,6803);\n+------------------------+\n| PERIOD_DIFF(7002,6803) |\n+------------------------+\n| -1177 |\n+------------------------+ +description=Returns the number of months between periods P1 and P2. P1 and P2 can be in\nthe format YYMM or YYYYMM, and are not date values.\n\nIf P1 or P2 contains a two-digit year, values from 00 to 69 are converted to\nfrom 2000 to 2069, while values from 70 are converted to 1970 upwards.\n\nExamples\n--------\n\nSELECT PERIOD_DIFF(200802,200703);\n+----------------------------+\n| PERIOD_DIFF(200802,200703) |\n+----------------------------+\n| 11 |\n+----------------------------+\n\nSELECT PERIOD_DIFF(6902,6803);\n+------------------------+\n| PERIOD_DIFF(6902,6803) |\n+------------------------+\n| 11 |\n+------------------------+\n\nSELECT PERIOD_DIFF(7002,6803);\n+------------------------+\n| PERIOD_DIFF(7002,6803) |\n+------------------------+\n| -1177 |\n+------------------------+\n\nURL: https://mariadb.com/kb/en/period_diff/ [PI] declaration= category=Numeric Functions -description=Returns the value of ? (pi). The default number of decimal\nplaces\ndisplayed is six, but MariaDB uses the full double-precision\nvalue\ninternally.\n \n\nSELECT PI();\n+----------+\n| PI() |\n+----------+\n| 3.141593 |\n+----------+\n \nSELECT PI()+0.0000000000000000000000;\n \n+-------------------------------+\n| PI()+0.0000000000000000000000 |\n+-------------------------------+\n| 3.1415926535897931159980 |\n+-------------------------------+ +description=Returns the value of π (pi). The default number of decimal places displayed is\nsix, but MariaDB uses the full double-precision value internally.\n\nExamples\n--------\n\nSELECT PI();\n+----------+\n| PI() |\n+----------+\n| 3.141593 |\n+----------+\n\nSELECT PI()+0.0000000000000000000000;\n+-------------------------------+\n| PI()+0.0000000000000000000000 |\n+-------------------------------+\n| 3.1415926535897931159980 |\n+-------------------------------+\n\nURL: https://mariadb.com/kb/en/pi/ [POINT] declaration=x,y category=Geometry Constructors -description=Constructs a WKB Point using the given coordinates.\n \n\nSET @g = ST_GEOMFROMTEXT(''Point(1 1)'');\n \nCREATE TABLE gis_point (g POINT);\nINSERT INTO gis_point VALUES\n (PointFromText(''POINT(10 10)'')),\n (PointFromText(''POINT(20 10)'')),\n (PointFromText(''POINT(20 20)'')),\n (PointFromWKB(AsWKB(PointFromText(''POINT(10 20)'')))); +description=Constructs a WKB Point using the given coordinates.\n\nExamples\n--------\n\nSET @g = ST_GEOMFROMTEXT('Point(1 1)');\n\nCREATE TABLE gis_point (g POINT);\nINSERT INTO gis_point VALUES\n (PointFromText('POINT(10 10)')),\n (PointFromText('POINT(20 10)')),\n (PointFromText('POINT(20 20)')),\n (PointFromWKB(AsWKB(PointFromText('POINT(10 20)'))));\n\nURL: https://mariadb.com/kb/en/point/ [POLYGON] declaration=ls1,ls2,... category=Geometry Constructors -description=Constructs a WKB Polygon value from a number of WKB\nLineString\narguments. If any argument does not represent the WKB of a\nLinearRing (that is,\nnot a closed and simple LineString) the return value is\nNULL.\n \nNote that according to the OpenGIS standard, a POLYGON\nshould have exactly one ExteriorRing and all other rings\nshould lie within that ExteriorRing and thus be the\nInteriorRings. Practically, however, some systems, including\nMariaDB''s, permit polygons to have several\n''ExteriorRings''. In the case of there being multiple,\nnon-overlapping exterior rings ST_NUMINTERIORRINGS() will\nreturn 1.\n \n\nSET @g = ST_GEOMFROMTEXT(''POLYGON((1 1,1 5,4 9,6 9,9 3,7\n2,1 1))'');\n \nCREATE TABLE gis_polygon (g POLYGON);\nINSERT INTO gis_polygon VALUES\n (PolygonFromText(''POLYGON((10 10,20 10,20 20,10 20,10\n10))'')),\n (PolyFromText(''POLYGON((0 0,50 0,50 50,0 50,0 0), (10\n10,20 10,20 20,10 20,10 10))'')),\n (PolyFromWKB(AsWKB(Polygon(LineString(Point(0, 0),\nPoint(30, 0), Point(30, 30), Point(0, 0))))));\n \nNon-overlapping ''polygon'':\n \nSELECT ST_NumInteriorRings(ST_PolyFromText(''POLYGON((0 0,10\n0,10 10,0 10,0 0),\n (-1 -1,-5 -1,-5 -5,-1 -5,-1 -1))'')) AS NumInteriorRings;\n \n+------------------+\n| NumInteriorRings |\n+------------------+\n| 1 |\n+------------------+ +description=Constructs a WKB Polygon value from a number of WKB LineString arguments. If\nany argument does not represent the WKB of a LinearRing (that is, not a closed\nand simple LineString) the return value is NULL.\n\nNote that according to the OpenGIS standard, a POLYGON should have exactly one\nExteriorRing and all other rings should lie within that ExteriorRing and thus\nbe the InteriorRings. Practically, however, some systems, including MariaDB's,\npermit polygons to have several 'ExteriorRings'. In the case of there being\nmultiple, non-overlapping exterior rings ST_NUMINTERIORRINGS() will return 1.\n\nExamples\n--------\n\nSET @g = ST_GEOMFROMTEXT('POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1))');\n\nCREATE TABLE gis_polygon (g POLYGON);\nINSERT INTO gis_polygon VALUES\n (PolygonFromText('POLYGON((10 10,20 10,20 20,10 20,10 10))')),\n (PolyFromText('POLYGON((0 0,50 0,50 50,0 50,0 0), (10 10,20 10,20 20,10\n20,10 10))')),\n (PolyFromWKB(AsWKB(Polygon(LineString(Point(0, 0), Point(30, 0), Point(30,\n30), Point(0, 0))))));\n\nNon-overlapping 'polygon':\n\nSELECT ST_NumInteriorRings(ST_PolyFromText('POLYGON((0 0,10 0,10 10,0 10,0 0),\n (-1 -1,-5 -1,-5 -5,-1 -5,-1 -1))')) AS NumInteriorRings;\n+------------------+\n| NumInteriorRings |\n+------------------+\n| 1 |\n+------------------+\n\nURL: https://mariadb.com/kb/en/polygon/ [POSITION] declaration=substr IN str category=String Functions -description=POSITION(substr IN str) is a synonym for LOCATE(substr,str).\n \nIt''s part of ODBC 3.0. -[POWER] +description=POSITION(substr IN str) is a synonym for LOCATE(substr,str).\n\nIt's part of ODBC 3.0.\n\nURL: https://mariadb.com/kb/en/position/ +[POW] declaration=X,Y category=Numeric Functions -description=This is a synonym for POW(), which returns the value of X\nraised to the power of Y. -[POW] +description=Returns the value of X raised to the power of Y.\n\nPOWER() is a synonym.\n\nExamples\n--------\n\nSELECT POW(2,3);\n+----------+\n| POW(2,3) |\n+----------+\n| 8 |\n+----------+\n\nSELECT POW(2,-2);\n+-----------+\n| POW(2,-2) |\n+-----------+\n| 0.25 |\n+-----------+\n\nURL: https://mariadb.com/kb/en/pow/ +[POWER] declaration=X,Y category=Numeric Functions -description=Returns the value of X raised to the power of Y.\n \nPOWER() is a synonym.\n \n\nSELECT POW(2,3);\n+----------+\n| POW(2,3) |\n+----------+\n| 8 |\n+----------+\n \nSELECT POW(2,-2);\n+-----------+\n| POW(2,-2) |\n+-----------+\n| 0.25 |\n+-----------+ +description=This is a synonym for POW(), which returns the value of X raised to the power\nof Y.\n\nURL: https://mariadb.com/kb/en/power/ [QUARTER] declaration=date category=Date and Time Functions -description=Returns the quarter of the year for date, in the range 1 to\n4. Returns 0 if month contains a zero value, or NULL if the\ngiven value is not otherwise a valid date (zero values are\naccepted).\n \n\nSELECT QUARTER(''2008-04-01'');\n+-----------------------+\n| QUARTER(''2008-04-01'') |\n+-----------------------+\n| 2 |\n+-----------------------+\n \nSELECT QUARTER(''2019-00-01'');\n+-----------------------+\n| QUARTER(''2019-00-01'') |\n+-----------------------+\n| 0 |\n+-----------------------+ +description=Returns the quarter of the year for date, in the range 1 to 4. Returns 0 if\nmonth contains a zero value, or NULL if the given value is not otherwise a\nvalid date (zero values are accepted).\n\nExamples\n--------\n\nSELECT QUARTER('2008-04-01');\n+-----------------------+\n| QUARTER('2008-04-01') |\n+-----------------------+\n| 2 |\n+-----------------------+\n\nSELECT QUARTER('2019-00-01');\n+-----------------------+\n| QUARTER('2019-00-01') |\n+-----------------------+\n| 0 |\n+-----------------------+\n\nURL: https://mariadb.com/kb/en/quarter/ [QUOTE] declaration=str category=String Functions -description=Quotes a string to produce a result that can be used as a\nproperly escaped data\nvalue in an SQL statement. The string is returned enclosed\nby single quotes and\nwith each instance of single quote ("''"), backslash\n("\"),\nASCII NUL, and Control-Z preceded by a backslash. If the\nargument\nis NULL, the return value is the word "NULL" without\nenclosing single\nquotes.\n \n\nSELECT QUOTE("Don''t!");\n+-----------------+\n| QUOTE("Don''t!") |\n+-----------------+\n| ''Don\''t!'' |\n+-----------------+\n \nSELECT QUOTE(NULL); \n+-------------+\n| QUOTE(NULL) |\n+-------------+\n| NULL |\n+-------------+ +description=Quotes a string to produce a result that can be used as a properly escaped\ndata value in an SQL statement. The string is returned enclosed by single\nquotes and with each instance of single quote ("'"), backslash ("\"), ASCII\nNUL, and Control-Z preceded by a backslash. If the argument is NULL, the\nreturn value is the word "NULL" without enclosing single quotes.\n\nExamples\n--------\n\nSELECT QUOTE("Don't!");\n+-----------------+\n| QUOTE("Don't!") |\n+-----------------+\n| 'Don\'t!' |\n+-----------------+\n\nSELECT QUOTE(NULL); \n+-------------+\n| QUOTE(NULL) |\n+-------------+\n| NULL |\n+-------------+\n\nURL: https://mariadb.com/kb/en/quote/ [RADIANS] declaration=X category=Numeric Functions -description=Returns the argument X, converted from degrees to radians.\nNote that\n? radians equals 180 degrees. \n \nThis is the converse of the DEGREES() function.\n \n\nSELECT RADIANS(45);\n+-------------------+\n| RADIANS(45) |\n+-------------------+\n| 0.785398163397448 |\n+-------------------+\n \nSELECT RADIANS(90);\n+-----------------+\n| RADIANS(90) |\n+-----------------+\n| 1.5707963267949 |\n+-----------------+\n \nSELECT RADIANS(PI());\n+--------------------+\n| RADIANS(PI()) |\n+--------------------+\n| 0.0548311355616075 |\n+--------------------+\n \nSELECT RADIANS(180);\n+------------------+\n| RADIANS(180) |\n+------------------+\n| 3.14159265358979 |\n+------------------+ -[RAND1] -name=RAND +description=Returns the argument X, converted from degrees to radians. Note that π radians\nequals 180 degrees.\n\nThis is the converse of the DEGREES() function.\n\nExamples\n--------\n\nSELECT RADIANS(45);\n+-------------------+\n| RADIANS(45) |\n+-------------------+\n| 0.785398163397448 |\n+-------------------+\n\nSELECT RADIANS(90);\n+-----------------+\n| RADIANS(90) |\n+-----------------+\n| 1.5707963267949 |\n+-----------------+\n\nSELECT RADIANS(PI());\n+--------------------+\n| RADIANS(PI()) |\n+--------------------+\n| 0.0548311355616075 |\n+--------------------+\n\nSELECT RADIANS(180);\n+------------------+\n| RADIANS(180) |\n+------------------+\n| 3.14159265358979 |\n+------------------+\n\nURL: https://mariadb.com/kb/en/radians/ +[RAND] declaration= category=Numeric Functions -description=Returns a random DOUBLE precision floating point value v in\nthe range 0 -[RAND2] -name=RAND -declaration=N -category=Numeric Functions -description=Returns a random DOUBLE precision floating point value v in\nthe range 0 -[RANK1] -name=RANK +description=Returns a random DOUBLE precision floating point value v in the range 0 <= v <\n1.0. If a constant integer argument N is specified, it is used as the seed\nvalue, which produces a repeatable sequence of column values. In the example\nbelow, note that the sequences of values produced by RAND(3) is the same both\nplaces where it occurs.\n\nIn a WHERE clause, RAND() is evaluated each time the WHERE is executed.\n\nStatements using the RAND() function are not safe for statement-based\nreplication.\n\nPractical uses\n--------------\n\nThe expression to get a random integer from a given range is the following:\n\nFLOOR(min_value + RAND() * (max_value - min_value +1))\n\nRAND() is often used to read random rows from a table, as follows:\n\nSELECT * FROM my_table ORDER BY RAND() LIMIT 10;\n\nNote, however, that this technique should never be used on a large table as it\nwill be extremely slow. MariaDB will read all rows in the table, generate a\nrandom value for each of them, order them, and finally will apply the LIMIT\nclause.\n\nExamples\n--------\n\nCREATE TABLE t (i INT);\n\nINSERT INTO t VALUES(1),(2),(3);\n\nSELECT i, RAND() FROM t;\n+------+-------------------+\n| i | RAND() |\n+------+-------------------+\n| 1 | 0.255651095188829 |\n| 2 | 0.833920199269355 |\n| 3 | 0.40264774151393 |\n+------+-------------------+\n\nSELECT i, RAND(3) FROM t;\n+------+-------------------+\n| i | RAND(3) |\n+------+-------------------+\n| 1 | 0.90576975597606 |\n| 2 | 0.373079058130345 |\n| 3 | 0.148086053457191 |\n ... +[RANDOM_BYTES] +declaration=length +category=Encryption Functions +description=Given a length from 1 to 1024, generates a binary string of length consisting\nof random bytes generated by the SSL library's random number generator.\n\nSee the RAND_bytes() function documentation of your SSL library for\ninformation on the random number generator. In the case of OpenSSL, a\ncryptographically secure pseudo random generator (CSPRNG) is used.\n\nStatements containing the RANDOM_BYTES function are unsafe for statement-based\nreplication.\n\nAn error occurs if length is outside the range 1 to 1024.\n\nURL: https://mariadb.com/kb/en/random_bytes/ +[RANK] declaration= category=Window Functions -description=RANK() is a window function that displays the number of a\ngiven row, starting at one and following the ORDER BY\nsequence of the window function, with identical values\nreceiving the same result. It is similar to the ROW_NUMBER()\nfunction except that in that function, identical values will\nreceive a different row number for each result.\n \n\nThe distinction between DENSE_RANK(), RANK() and\nROW_NUMBER():\n \nCREATE TABLE student(course VARCHAR(10), mark int, name\nvarchar(10));\n \nINSERT INTO student VALUES \n (''Maths'', 60, ''Thulile''),\n (''Maths'', 60, ''Pritha''),\n (''Maths'', 70, ''Voitto''),\n (''Maths'', 55, ''Chun''),\n (''Biology'', 60, ''Bilal''),\n (''Biology'', 70, ''Roger'');\n \nSELECT \n RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS\nrank, \n DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC)\nAS dense_rank, \n ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC)\nAS row_num, \n course, mark, name \nFROM student ORDER BY course, mark DESC;\n \n+------+------------+---------+---------+------+---------+\n| rank | dense_rank | row_num | course | mark | name |\n+------+------------+---------+---------+------+---------+\n| 1 | 1 | 1 | Biology | 70 | Roger |\n| 2 | 2 | 2 | Biology | 60 | Bilal |\n| 1 | 1 | 1 | Maths | 70 | Voitto |\n| 2 | 2 | 2 | Maths | 60 | Thulile |\n| 2 | 2 | 3 | Maths | 60 | Pritha |\n| 4 | 3 | 4 | Maths | 55 | Chun |\n+------+------------+---------+---------+------+---------+ -[RANK2] -name=RANK -declaration=[ PARTITION BY partition_expression ] [ ORDER BY order_list ] -category=Window Functions -description=RANK() is a window function that displays the number of a\ngiven row, starting at one and following the ORDER BY\nsequence of the window function, with identical values\nreceiving the same result. It is similar to the ROW_NUMBER()\nfunction except that in that function, identical values will\nreceive a different row number for each result.\n \n\nThe distinction between DENSE_RANK(), RANK() and\nROW_NUMBER():\n \nCREATE TABLE student(course VARCHAR(10), mark int, name\nvarchar(10));\n \nINSERT INTO student VALUES \n (''Maths'', 60, ''Thulile''),\n (''Maths'', 60, ''Pritha''),\n (''Maths'', 70, ''Voitto''),\n (''Maths'', 55, ''Chun''),\n (''Biology'', 60, ''Bilal''),\n (''Biology'', 70, ''Roger'');\n \nSELECT \n RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS\nrank, \n DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC)\nAS dense_rank, \n ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC)\nAS row_num, \n course, mark, name \nFROM student ORDER BY course, mark DESC;\n \n+------+------------+---------+---------+------+---------+\n| rank | dense_rank | row_num | course | mark | name |\n+------+------------+---------+---------+------+---------+\n| 1 | 1 | 1 | Biology | 70 | Roger |\n| 2 | 2 | 2 | Biology | 60 | Bilal |\n| 1 | 1 | 1 | Maths | 70 | Voitto |\n| 2 | 2 | 2 | Maths | 60 | Thulile |\n| 2 | 2 | 3 | Maths | 60 | Pritha |\n| 4 | 3 | 4 | Maths | 55 | Chun |\n+------+------------+---------+---------+------+---------+ -[REGEXP_INSTR10] -name=REGEXP_INSTR -declaration=''ABC'' COLLATE utf8_bin,''b'' -category=String Functions -description= -[REGEXP_INSTR11] -name=REGEXP_INSTR -declaration=BINARY''ABC'',''b'' -category=String Functions -description= -[REGEXP_INSTR12] -name=REGEXP_INSTR -declaration=''ABC'',''(?-i)b'' -category=String Functions -description= -[REGEXP_INSTR13] -name=REGEXP_INSTR -declaration=''ABC'' COLLATE utf8_bin,''(?i)b'' -category=String Functions -description= -[REGEXP_INSTR1] -name=REGEXP_INSTR +description=RANK() is a window function that displays the number of a given row, starting\nat one and following the ORDER BY sequence of the window function, with\nidentical values receiving the same result. It is similar to the ROW_NUMBER()\nfunction except that in that function, identical values will receive a\ndifferent row number for each result.\n\nExamples\n--------\n\nThe distinction between DENSE_RANK(), RANK() and ROW_NUMBER():\n\nCREATE TABLE student(course VARCHAR(10), mark int, name varchar(10));\n\nINSERT INTO student VALUES \n ('Maths', 60, 'Thulile'),\n ('Maths', 60, 'Pritha'),\n ('Maths', 70, 'Voitto'),\n ('Maths', 55, 'Chun'),\n ('Biology', 60, 'Bilal'),\n ('Biology', 70, 'Roger');\n\nSELECT \n RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS rank,\n DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS dense_rank,\n ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC) AS row_num,\n course, mark, name\nFROM student ORDER BY course, mark DESC;\n+------+------------+---------+---------+------+---------+\n| rank | dense_rank | row_num | course | mark | name |\n+------+------------+---------+---------+------+---------+\n| 1 | 1 | 1 | Biology | 70 | Roger |\n| 2 | 2 | 2 | Biology | 60 | Bilal |\n| 1 | 1 | 1 | Maths | 70 | Voitto |\n| 2 | 2 | 2 | Maths | 60 | Thulile |\n| 2 | 2 | 3 | Maths | 60 | Pritha |\n| 4 | 3 | 4 | Maths | 55 | Chun |\n+------+------------+---------+---------+------+---------+\n\nURL: https://mariadb.com/kb/en/rank/ +[REGEXP_INSTR] declaration=subject, pattern category=String Functions -description= -[REGEXP_INSTR2] -name=REGEXP_INSTR -declaration=i.e. not in bytes -category=String Functions -description= -[REGEXP_INSTR3] -name=REGEXP_INSTR -declaration=?i -category=String Functions -description= -[REGEXP_INSTR4] -name=REGEXP_INSTR -declaration=?-i -category=String Functions -description= -[REGEXP_INSTR5] -name=REGEXP_INSTR -declaration=''abc'',''b'' -category=String Functions -description= -[REGEXP_INSTR6] -name=REGEXP_INSTR -declaration=''abc'',''x'' -category=String Functions -description= -[REGEXP_INSTR7] -name=REGEXP_INSTR -declaration=''BJRN'',''N'' -category=String Functions -description= -[REGEXP_INSTR8] -name=REGEXP_INSTR -declaration=BINARY ''BJRN'',''N'' -category=String Functions -description= -[REGEXP_INSTR9] -name=REGEXP_INSTR -declaration=''ABC'',''b'' -category=String Functions -description= +description=Returns the position of the first occurrence of the regular expression pattern\nin the string subject, or 0 if pattern was not found.\n\nThe positions start with 1 and are measured in characters (i.e. not in bytes),\nwhich is important for multi-byte character sets. You can cast a multi-byte\ncharacter set to BINARY to get offsets in bytes.\n\nThe function follows the case sensitivity rules of the effective collation.\nMatching is performed case insensitively for case insensitive collations, and\ncase sensitively for case sensitive collations and for binary data.\n\nThe collation case sensitivity can be overwritten using the (?i) and (?-i)\nPCRE flags.\n\nMariaDB uses the PCRE regular expression library for enhanced regular\nexpression performance, and REGEXP_INSTR was introduced as part of this\nenhancement.\n\nExamples\n--------\n\nSELECT REGEXP_INSTR('abc','b');\n-> 2\n\nSELECT REGEXP_INSTR('abc','x');\n-> 0\n\nSELECT REGEXP_INSTR('BJÖRN','N');\n-> 5\n\nCasting a multi-byte character set as BINARY to get offsets in bytes:\n\nSELECT REGEXP_INSTR(BINARY 'BJÖRN','N') AS cast_utf8_to_binary;\n-> 6\n\nCase sensitivity:\n\nSELECT REGEXP_INSTR('ABC','b');\n-> 2\n\nSELECT REGEXP_INSTR('ABC' COLLATE utf8_bin,'b');\n-> 0\n\nSELECT REGEXP_INSTR(BINARY'ABC','b');\n-> 0\n\nSELECT REGEXP_INSTR('ABC','(?-i)b');\n-> 0\n\nSELECT REGEXP_INSTR('ABC' COLLATE utf8_bin,'(?i)b');\n-> 2\n\nURL: https://mariadb.com/kb/en/regexp_instr/ [REGEXP_REPLACE] declaration=subject, pattern, replace category=String Functions -description=REGEXP_REPLACE returns the string subject with all\noccurrences of the regular expression pattern replaced by\nthe string replace. If no occurrences are found, then\nsubject is returned as is.\n \nThe replace string can have backreferences to the\nsubexpressions in the form \N, where N is a number from 1\nto 9.\n \nThe function follows the case sensitivity rules of the\neffective collation. Matching is performed case\ninsensitively for case insensitive collations, and case\nsensitively for case sensitive collations and for binary\ndata.\n \nThe collation case sensitivity can be overwritten using the\n(?i) and (?-i) PCRE flags.\n \nMariaDB 10.0.5 switched to the PCRE regular expression\nlibrary for enhanced regular expression performance, and\nREGEXP_REPLACE was introduced as part of this enhancement.\n \nMariaDB 10.0.11 introduced the default_regex_flags variable\nto address the remaining compatibilities between PCRE and\nthe old regex library. \n \n\nSELECT REGEXP_REPLACE(''ab12cd'',''[0-9]'','''') AS\nremove_digits;\n-> abcd\n \nSELECT REGEXP_REPLACE(''titlebody'', '''','' '')\nAS strip_html;\n-> title body\n \nBackreferences to the subexpressions in the form \N, where\nN is a number from 1 to 9:\n \nSELECT REGEXP_REPLACE(''James Bond'',''^(.*)\n(.*)$'',''\\2, \\1'') AS reorder_name;\n-> Bond, James\n \nCase insensitive and case sensitive matches:\n \nSELECT REGEXP_REPLACE(''ABC'',''b'',''-'') AS\ncase_insensitive;\n-> A-C\n \nSELECT REGEXP_REPLACE(''ABC'' COLLATE utf8_bin,''b'',''-'')\nAS case_sensitive;\n-> ABC\n \nSELECT REGEXP_REPLACE(BINARY ''ABC'',''b'',''-'') AS\nbinary_data;\n-> ABC\n \nOverwriting the collation case sensitivity using the (?i)\nand (?-i) PCRE flags.\n \nSELECT REGEXP_REPLACE(''ABC'',''(?-i)b'',''-'') AS\nforce_case_sensitive;\n-> ABC\n \nSELECT REGEXP_REPLACE(BINARY ''ABC'',''(?i)b'',''-'') AS\nforce_case_insensitive;\n-> A-C +description=REGEXP_REPLACE returns the string subject with all occurrences of the regular\nexpression pattern replaced by the string replace. If no occurrences are\nfound, then subject is returned as is.\n\nThe replace string can have backreferences to the subexpressions in the form\n\N, where N is a number from 1 to 9.\n\nThe function follows the case sensitivity rules of the effective collation.\nMatching is performed case insensitively for case insensitive collations, and\ncase sensitively for case sensitive collations and for binary data.\n\nThe collation case sensitivity can be overwritten using the (?i) and (?-i)\nPCRE flags.\n\nMariaDB uses the PCRE regular expression library for enhanced regular\nexpression performance, and REGEXP_REPLACE was introduced as part of this\nenhancement.\n\nThe default_regex_flags variable addresses the remaining compatibilities\nbetween PCRE and the old regex library.\n\nExamples\n--------\n\nSELECT REGEXP_REPLACE('ab12cd','[0-9]','') AS remove_digits;\n-> abcd\n\nSELECT\nREGEXP_REPLACE('titlebody',\n'<.+?>',' ')\nAS strip_html;\n-> title body\n\nBackreferences to the subexpressions in the form \N, where N is a number from\n1 to 9:\n\nSELECT REGEXP_REPLACE('James Bond','^(.*) (.*)$','\\2, \\1') AS reorder_name;\n-> Bond, James\n\nCase insensitive and case sensitive matches:\n\nSELECT REGEXP_REPLACE('ABC','b','-') AS case_insensitive;\n-> A-C\n\nSELECT REGEXP_REPLACE('ABC' COLLATE utf8_bin,'b','-') AS case_sensitive;\n-> ABC\n\nSELECT REGEXP_REPLACE(BINARY 'ABC','b','-') AS binary_data;\n-> ABC\n\n ... [REGEXP_SUBSTR] declaration=subject,pattern category=String Functions -description=Returns the part of the string subject that matches the\nregular expression pattern, or an empty string if pattern\nwas not found.\n \nThe function follows the case sensitivity rules of the\neffective collation. Matching is performed case\ninsensitively for case insensitive collations, and case\nsensitively for case sensitive collations and for binary\ndata.\n \nThe collation case sensitivity can be overwritten using the\n(?i) and (?-i) PCRE flags.\n \nMariaDB 10.0.5 switched to the PCRE regular expression\nlibrary for enhanced regular expression performance, and\nREGEXP_SUBSTR was introduced as part of this enhancement.\n \nMariaDB 10.0.11 introduced the default_regex_flags variable\nto address the remaining compatibilities between PCRE and\nthe old regex library. \n \n\nSELECT REGEXP_SUBSTR(''ab12cd'',''[0-9]+'');\n-> 12\n \nSELECT REGEXP_SUBSTR(\n ''See https://mariadb.org/en/foundation/ for details'',\n ''https?://[^/]*'');\n-> https://mariadb.org\n \nSELECT REGEXP_SUBSTR(''ABC'',''b'');\n-> B\n \nSELECT REGEXP_SUBSTR(''ABC'' COLLATE utf8_bin,''b'');\n->\n \nSELECT REGEXP_SUBSTR(BINARY''ABC'',''b'');\n->\n \nSELECT REGEXP_SUBSTR(''ABC'',''(?i)b'');\n-> B\n \nSELECT REGEXP_SUBSTR(''ABC'' COLLATE utf8_bin,''(?+i)b'');\n-> B +description=Returns the part of the string subject that matches the regular expression\npattern, or an empty string if pattern was not found.\n\nThe function follows the case sensitivity rules of the effective collation.\nMatching is performed case insensitively for case insensitive collations, and\ncase sensitively for case sensitive collations and for binary data.\n\nThe collation case sensitivity can be overwritten using the (?i) and (?-i)\nPCRE flags.\n\nMariaDB uses the PCRE regular expression library for enhanced regular\nexpression performance, and REGEXP_SUBSTR was introduced as part of this\nenhancement.\n\nThe default_regex_flags variable addresses the remaining compatibilities\nbetween PCRE and the old regex library.\n\nExamples\n--------\n\nSELECT REGEXP_SUBSTR('ab12cd','[0-9]+');\n-> 12\n\nSELECT REGEXP_SUBSTR(\n 'See https://mariadb.org/en/foundation/ for details',\n 'https?://[^/]*');\n-> https://mariadb.org\n\nSELECT REGEXP_SUBSTR('ABC','b');\n-> B\n\nSELECT REGEXP_SUBSTR('ABC' COLLATE utf8_bin,'b');\n->\n\nSELECT REGEXP_SUBSTR(BINARY'ABC','b');\n->\n\nSELECT REGEXP_SUBSTR('ABC','(?i)b');\n-> B\n\nSELECT REGEXP_SUBSTR('ABC' COLLATE utf8_bin,'(?+i)b');\n-> B\n\nURL: https://mariadb.com/kb/en/regexp_substr/ +[RELEASE_ALL_LOCKS] +declaration= +category=Miscellaneous Functions +description=Releases all named locks held by the current session. Returns the number of\nlocks released, or 0 if none were held.\n\nStatements using the RELEASE_ALL_LOCKS function are not safe for\nstatement-based replication.\n\nExamples\n--------\n\nSELECT RELEASE_ALL_LOCKS();\n+---------------------+\n| RELEASE_ALL_LOCKS() | \n+---------------------+\n| 0 |\n+---------------------+\n\nSELECT GET_LOCK('lock1',10);\n+----------------------+\n| GET_LOCK('lock1',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSELECT RELEASE_ALL_LOCKS();\n+---------------------+\n| RELEASE_ALL_LOCKS() | \n+---------------------+\n| 1 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/release_all_locks/ [RELEASE_LOCK] declaration=str category=Miscellaneous Functions -description=Releases the lock named by the string str that was obtained\nwith GET_LOCK(). Returns 1 if the lock was released, 0 if\nthe lock was not established by this thread (in which case\nthe lock is not\nreleased), and NULL if the named lock did not exist. The\nlock does not exist if it was never obtained by a call to\nGET_LOCK() or if it has previously been released.\n \nMariaDB until 10.0.1\n \nBefore 10.0.2, GET_LOCK() released the existing lock, if\nany. Since 10.0.2 this does not happen, because multiple\nlocks are allowed.\n \nstr is case insensitive. If str is an empty string or NULL,\nRELEASE_LOCK() returns NULL and does nothing.\n \nStatements using the RELEASE_LOCK() function are not safe\nfor replication.\n \nThe DO statement is convenient to use with RELEASE_LOCK().\n \n\nConnection1:\n \nSELECT GET_LOCK(''lock1'',10);\n+----------------------+\n| GET_LOCK(''lock1'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nConnection 2:\n \nSELECT GET_LOCK(''lock2'',10);\n+----------------------+\n| GET_LOCK(''lock2'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nConnection 1:\n \nSELECT RELEASE_LOCK(''lock1''), RELEASE_LOCK(''lock2''),\nRELEASE_LOCK(''lock3'');\n+-----------------------+-----------------------+-----------------------+\n| RELEASE_LOCK(''lock1'') | RELEASE_LOCK(''lock2'') |\nRELEASE_LOCK(''lock3'') |\n+-----------------------+-----------------------+-----------------------+\n| 1 | 0 | NULL |\n+-----------------------+-----------------------+-----------------------+\n \nFrom MariaDB 10.0.2, it is possible to hold the same lock\nrecursively. This example is viewed using the\nmetadata_lock_info plugin:\n \nSELECT GET_LOCK(''lock3'',10);\n+----------------------+\n| GET_LOCK(''lock3'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nSELECT GET_LOCK(''lock3'',10);\n+----------------------+\n| GET_LOCK(''lock3'',10) |\n+----------------------+\n| 1 |\n+----------------------+\n \nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\n \n+-----------+---------------------+---------------+-----------+--------------+------------+\n| THREAD_ID | LOCK_MODE | LOCK_DURATION | LOCK_TYPE |\nTABLE_SCHEMA | TABLE_NAME |\n+-----------+---------------------+---------------+-----------+--------------+------------+\n| 46 | MDL_SHARED_NO_WRITE | NULL | User lock | lock3 | |\n+-----------+---------------------+---------------+-----------+--------------+------------+\n \nSELECT RELEASE_LOCK(''lock3'');\n+-----------------------+\n| RELEASE_LOCK(''lock3'') |\n+-----------------------+\n| 1 |\n+-----------------------+\n \nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\n \n+-----------+---------------------+---------------+-----------+--------------+------------+\n| THREAD_ID | LOCK_MODE | LOCK_DURATION | LOCK_TYPE |\nTABLE_SCHEMA | TABLE_NAME |\n+-----------+---------------------+---------------+-----------+--------------+------------+\n| 46 | MDL_SHARED_NO_WRITE | NULL | User lock | lock3 | |\n+-----------+---------------------+---------------+-----------+--------------+------------+\n \nSELECT RELEASE_LOCK(''lock3'');\n+-----------------------+\n| RELEASE_LOCK(''lock3'') |\n+-----------------------+\n| 1 |\n+-----------------------+\n \nSELECT * FROM INFORMATION_SCHEMA.METADATA_LOCK_INFO;\n \nEmpty set (0.000 sec) +description=Releases the lock named by the string str that was obtained with GET_LOCK().\nReturns 1 if the lock was released, 0 if the lock was not established by this\nthread (in which case the lock is not released), and NULL if the named lock\ndid not exist. The lock does not exist if it was never obtained by a call to\nGET_LOCK() or if it has previously been released.\n\nstr is case insensitive. If str is an empty string or NULL, RELEASE_LOCK()\nreturns NULL and does nothing.\n\nStatements using the RELEASE_LOCK function are not safe for statement-based\nreplication.\n\nThe DO statement is convenient to use with RELEASE_LOCK().\n\nExamples\n--------\n\nConnection1:\n\nSELECT GET_LOCK('lock1',10);\n+----------------------+\n| GET_LOCK('lock1',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nConnection 2:\n\nSELECT GET_LOCK('lock2',10);\n+----------------------+\n| GET_LOCK('lock2',10) |\n+----------------------+\n| 1 |\n+----------------------+\n\nConnection 1:\n\nSELECT RELEASE_LOCK('lock1'), RELEASE_LOCK('lock2'), RELEASE_LOCK('lock3');\n+-----------------------+-----------------------+-----------------------+\n| RELEASE_LOCK('lock1') | RELEASE_LOCK('lock2') | RELEASE_LOCK('lock3') |\n+-----------------------+-----------------------+-----------------------+\n| 1 | 0 | NULL |\n+-----------------------+-----------------------+-----------------------+\n\nIt is possible to hold the same lock recursively. This example is viewed using\nthe metadata_lock_info plugin:\n\nSELECT GET_LOCK('lock3',10);\n+----------------------+\n| GET_LOCK('lock3',10) |\n ... +[RETURN] +declaration=SELECT COUNT(DISTINCT User +category=Compound Statements +description=END;\n\nURL: https://mariadb.com/kb/en/return/ [REVERSE] declaration=str category=String Functions -description=Returns the string str with the order of the characters\nreversed.\n \n\nSELECT REVERSE(''desserts'');\n+---------------------+\n| REVERSE(''desserts'') |\n+---------------------+\n| stressed |\n+---------------------+ +description=Returns the string str with the order of the characters reversed.\n\nExamples\n--------\n\nSELECT REVERSE('desserts');\n+---------------------+\n| REVERSE('desserts') |\n+---------------------+\n| stressed |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/reverse/ [RIGHT] declaration=str,len category=String Functions -description=Returns the rightmost len characters from the string str, or\nNULL if\nany argument is NULL.\n \n\nSELECT RIGHT(''MariaDB'', 2);\n+---------------------+\n| RIGHT(''MariaDB'', 2) |\n+---------------------+\n| DB |\n+---------------------+ -[ROUND1] -name=ROUND +description=Returns the rightmost len characters from the string str, or NULL if any\nargument is NULL.\n\nExamples\n--------\n\nSELECT RIGHT('MariaDB', 2);\n+---------------------+\n| RIGHT('MariaDB', 2) |\n+---------------------+\n| DB |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/right/ +[ROLLBACK] +declaration=the keyword WORK is simply noise and can be omitted without changing the effect +category=Transactions +description=The optional AND CHAIN clause is a convenience for initiating a new\ntransaction as soon as the old transaction terminates. If AND CHAIN is\nspecified, then there is effectively nothing between the old and new\ntransactions, although they remain separate. The characteristics of the new\ntransaction will be the same as the characteristics of the old one - that is,\nthe new transaction will have the same access mode, isolation level and\ndiagnostics area size (we'll discuss all of these shortly) as the transaction\njust terminated. The AND NO CHAIN option just tells your DBMS to end the\ntransaction - that is, these four SQL statements are equivalent:\n\nROLLBACK; \nROLLBACK WORK; \nROLLBACK AND NO CHAIN; \nROLLBACK WORK AND NO CHAIN;\n\nAll of them end a transaction without saving any transaction characteristics.\nThe only other options, the equivalent statements:\n\nROLLBACK AND CHAIN;\nROLLBACK WORK AND CHAIN;\n\nboth tell your DBMS to end a transaction, but to save that transaction's\ncharacteristics for the next transaction.\n\nROLLBACK is much simpler than COMMIT: it may involve no more than a few\ndeletions (of Cursors, locks, prepared SQL statements and log-file entries).\nIt's usually assumed that ROLLBACK can't fail, although such a thing is\nconceivable (for example, an encompassing transaction might reject an attempt\nto ROLLBACK because it's lining up for a COMMIT).\n\nROLLBACK cancels all effects of a transaction. It does not cancel effects on\nobjects outside the DBMS's control (for example the values in host program\nvariables or the settings made by some SQL/CLI function calls). But in\ngeneral, it is a convenient statement for those situations when you say "oops,\nthis isn't working" or when you simply don't care whether your temporary work\nbecomes permanent or not.\n\nHere is a moot question. If all you've been doing is SELECTs, so that there\nhave been no data changes, should you end the transaction with ROLLBACK or\nCOMMIT? It shouldn't really matter because both ROLLBACK and COMMIT do the\nsame transaction-terminating job. However, the popular conception is that\nROLLBACK implies failure, so after a successful series of SELECT statements\nthe convention is to end the transaction with COMMIT rather than ROLLBACK.\n\nMariaDB (and most other DBMSs) supports rollback of SQL-data change\nstatements, but not of SQL-Schema statements. This means that if you use any\nof CREATE, ALTER, DROP, GRANT, REVOKE, you are implicitly committing at\nexecution time.\n\nINSERT INTO Table_2 VALUES(5); \n ... +[ROUND] declaration=X category=Numeric Functions -description=Rounds the argument X to D decimal places. The rounding\nalgorithm\ndepends on the data type of X. D defaults to 0 if not\nspecified.\nD can be negative to cause D digits left of the decimal\npoint of the\nvalue X to become zero.\n \n\nSELECT ROUND(-1.23);\n+--------------+\n| ROUND(-1.23) |\n+--------------+\n| -1 |\n+--------------+\n \nSELECT ROUND(-1.58);\n+--------------+\n| ROUND(-1.58) |\n+--------------+\n| -2 |\n+--------------+\n \nSELECT ROUND(1.58); \n+-------------+\n| ROUND(1.58) |\n+-------------+\n| 2 |\n+-------------+\n \nSELECT ROUND(1.298, 1);\n+-----------------+\n| ROUND(1.298, 1) |\n+-----------------+\n| 1.3 |\n+-----------------+\n \nSELECT ROUND(1.298, 0);\n+-----------------+\n| ROUND(1.298, 0) |\n+-----------------+\n| 1 |\n+-----------------+\n \nSELECT ROUND(23.298, -1);\n+-------------------+\n| ROUND(23.298, -1) |\n+-------------------+\n| 20 |\n+-------------------+ -[ROUND2] -name=ROUND -declaration=X,D -category=Numeric Functions -description=Rounds the argument X to D decimal places. The rounding\nalgorithm\ndepends on the data type of X. D defaults to 0 if not\nspecified.\nD can be negative to cause D digits left of the decimal\npoint of the\nvalue X to become zero.\n \n\nSELECT ROUND(-1.23);\n+--------------+\n| ROUND(-1.23) |\n+--------------+\n| -1 |\n+--------------+\n \nSELECT ROUND(-1.58);\n+--------------+\n| ROUND(-1.58) |\n+--------------+\n| -2 |\n+--------------+\n \nSELECT ROUND(1.58); \n+-------------+\n| ROUND(1.58) |\n+-------------+\n| 2 |\n+-------------+\n \nSELECT ROUND(1.298, 1);\n+-----------------+\n| ROUND(1.298, 1) |\n+-----------------+\n| 1.3 |\n+-----------------+\n \nSELECT ROUND(1.298, 0);\n+-----------------+\n| ROUND(1.298, 0) |\n+-----------------+\n| 1 |\n+-----------------+\n \nSELECT ROUND(23.298, -1);\n+-------------------+\n| ROUND(23.298, -1) |\n+-------------------+\n| 20 |\n+-------------------+ +description=Rounds the argument X to D decimal places. D defaults to 0 if not specified. D\ncan be negative to cause D digits left of the decimal point of the value X to\nbecome zero.\n\nThe rounding algorithm depends on the data type of X:\n\n* for floating point types (FLOAT, DOUBLE) the C libraries rounding function\nis used, so the behavior *may* differ between operating systems\n* for fixed point types (DECIMAL, DEC/NUMBER/FIXED) the "round half up" rule\nis used, meaning that e.g. a value ending in exactly .5 is always rounded up.\n\nExamples\n--------\n\nSELECT ROUND(-1.23);\n+--------------+\n| ROUND(-1.23) |\n+--------------+\n| -1 |\n+--------------+\n\nSELECT ROUND(-1.58);\n+--------------+\n| ROUND(-1.58) |\n+--------------+\n| -2 |\n+--------------+\n\nSELECT ROUND(1.58); \n+-------------+\n| ROUND(1.58) |\n+-------------+\n| 2 |\n+-------------+\n\nSELECT ROUND(1.298, 1);\n+-----------------+\n| ROUND(1.298, 1) |\n+-----------------+\n| 1.3 |\n+-----------------+\n\nSELECT ROUND(1.298, 0);\n+-----------------+\n| ROUND(1.298, 0) |\n+-----------------+\n| 1 |\n+-----------------+\n\nSELECT ROUND(23.298, -1);\n ... +[ROW] +declaration= [{, }... ] +category=Data Types +description=ROW is a data type for stored procedure variables.\n\nFeatures\n--------\n\nROW fields as normal variables\n------------------------------\n\nROW fields (members) act as normal variables, and are able to appear in all\nquery parts where a stored procedure variable is allowed:\n\n* Assignment is using the := operator and the SET command:\n\na.x:= 10;\na.x:= b.x;\nSET a.x= 10, a.y=20, a.z= b.z;\n\n* Passing to functions and operators:\n\nSELECT f1(rec.a), rec.a<10;\n\n* Clauses (select list, WHERE, HAVING, LIMIT, etc...,):\n\nSELECT var.a, t1.b FROM t1 WHERE t1.b=var.b LIMIT var.c;\n\n* INSERT values:\n\nINSERT INTO t1 VALUES (rec.a, rec.b, rec.c);\n\n* SELECT .. INTO targets\n\nSELECT a,b INTO rec.a, rec.b FROM t1 WHERE t1.id=10;\n\n* Dynamic SQL out parameters (EXECUTE and EXECUTE IMMEDIATE)\n\nEXECUTE IMMEDIATE 'CALL proc_with_out_param(?)' USING rec.a;\n\nROW type variables as FETCH targets\n-----------------------------------\n\nROW type variables are allowed as FETCH targets:\n\nFETCH cur INTO rec;\n\nwhere cur is a CURSOR and rec is a ROW type stored procedure variable.\n\nNote, currently an attempt to use FETCH for a ROW type variable returns this\nerror:\n\nERROR 1328 (HY000): Incorrect number of FETCH variables\n ... +[ROWNUM] +declaration= +category=Information Functions +description=ROWNUM() returns the current number of accepted rows in the current context.\nIt main purpose is to emulate the ROWNUM pseudo column in Oracle. For MariaDB\nnative applications, we recommend the usage of LIMIT, as it is easier to use\nand gives more predictable results than the usage of ROWNUM().\n\nThe main difference between using LIMIT and ROWNUM() to limit the rows in the\nresult is that LIMIT works on the result set while ROWNUM works on the number\nof accepted rows (before any ORDER or GROUP BY clauses).\n\nThe following queries will return the same results:\n\nSELECT * from t1 LIMIT 10;\nSELECT * from t1 WHERE ROWNUM() <= 10;\n\nWhile the following may return different results based on in which orders the\nrows are found:\n\nSELECT * from t1 ORDER BY a LIMIT 10;\nSELECT * from t1 ORDER BY a WHERE ROWNUM() <= 10;\n\nThe recommended way to use ROWNUM to limit the number of returned rows and get\npredictable results is to have the query in a subquery and test for ROWNUM()\nin the outer query:\n\nSELECT * FROM (select * from t1 ORDER BY a) WHERE ROWNUM() <= 10;\n\nROWNUM() can be used in the following contexts:\n\n* SELECT\n* INSERT\n* UPDATE\n* DELETE\n* LOAD DATA INFILE\n\nUsed in other contexts, ROWNUM() will return 0.\n\nExamples\n--------\n\nINSERT INTO t1 VALUES (1,ROWNUM()),(2,ROWNUM()),(3,ROWNUM());\n\nINSERT INTO t1 VALUES (1),(2) returning a, ROWNUM();\n\nUPDATE t1 SET row_num_column=ROWNUM();\n\nDELETE FROM t1 WHERE a < 10 AND ROWNUM() < 2;\n\nLOAD DATA INFILE 'filename' into table t1 fields terminated by ',' \n lines terminated by "\r\n" (a,b) set c=ROWNUM();\n\n ... [ROW_COUNT] declaration= category=Information Functions -description=ROW_COUNT() returns the number of rows updated, inserted or\ndeleted\nby the preceding statement. This is the same as the row\ncount that the\nmysql client displays and the value from the\nmysql_affected_rows() C\nAPI function.\n \nGenerally:\nFor statements which return a result set (such as SELECT,\nSHOW, DESC or HELP), returns -1, even when the result set is\nempty. This is also true for administrative statements, such\nas OPTIMIZE.\nFor DML statements other than SELECT and for ALTER TABLE,\nreturns the number of affected rows.\nFor DDL statements (including TRUNCATE) and for other\nstatements which don''t return any result set (such as USE,\nDO, SIGNAL or DEALLOCATE PREPARE), returns 0.\n \nFor UPDATE, affected rows is by default the number of rows\nthat were actually changed. If the CLIENT_FOUND_ROWS flag to\nmysql_real_connect() is specified when connecting to mysqld,\naffected rows is instead the number of rows matched by the\nWHERE clause. \n \nFor REPLACE, deleted rows are also counted. So, if REPLACE\ndeletes a row and adds a new row, ROW_COUNT() returns 2.\n \nFor INSERT ... ON DUPLICATE KEY, updated rows are counted\ntwice. So, if INSERT adds a new rows and modifies another\nrow, ROW_COUNT() returns 3.\n \nROW_COUNT() does not take into account rows that are not\ndirectly deleted/updated by the last statement. This means\nthat rows deleted by foreign keys or triggers are not\ncounted.\n \nWarning: You can use ROW_COUNT() with prepared statements,\nbut you need to call it after EXECUTE, not after DEALLOCATE\nPREPARE, because the row count for allocate prepare is\nalways 0.\n \nWarning: When used after a CALL statement, this function\nreturns the number of rows affected by the last statement in\nthe procedure, not by the whole procedure.\n \nWarning: After INSERT DELAYED, ROW_COUNT() returns the\nnumber of the rows you tried to insert, not the number of\nthe successful writes.\n \nThis information can also be found in the diagnostics area.\n \nStatements using the ROW_COUNT() function are not safe for\nreplication.\n \n\nCREATE TABLE t (A INT);\n \nINSERT INTO t VALUES(1),(2),(3);\n \nSELECT ROW_COUNT();\n+-------------+\n| ROW_COUNT() |\n+-------------+\n| 3 |\n+-------------+\n \nDELETE FROM t WHERE A IN(1,2);\n \nSELECT ROW_COUNT(); \n+-------------+\n| ROW_COUNT() |\n+-------------+\n| 2 |\n+-------------+\n \nExample with prepared statements:\n \nSET @q = ''INSERT INTO t VALUES(1),(2),(3);'';\n \nPREPARE stmt FROM @q;\n \nEXECUTE stmt;\n \nQuery OK, 3 rows affected (0.39 sec)\nRecords: 3 Duplicates: 0 Warnings: 0\n \nSELECT ROW_COUNT();\n+-------------+\n| ROW_COUNT() |\n+-------------+\n| 3 |\n+-------------+ -[ROW_NUMBER1] -name=ROW_NUMBER +description=ROW_COUNT() returns the number of rows updated, inserted or deleted by the\npreceding statement. This is the same as the row count that the mariadb client\ndisplays and the value from the mysql_affected_rows() C API function.\n\nGenerally:\n\n* For statements which return a result set (such as SELECT, SHOW, DESC or\nHELP), returns -1, even when the result set is empty. This is also true for\nadministrative statements, such as OPTIMIZE.\n* For DML statements other than SELECT and for ALTER TABLE, returns the number\nof affected rows.\n* For DDL statements (including TRUNCATE) and for other statements which don't\nreturn any result set (such as USE, DO, SIGNAL or DEALLOCATE PREPARE), returns\n0.\n\nFor UPDATE, affected rows is by default the number of rows that were actually\nchanged. If the CLIENT_FOUND_ROWS flag to mysql_real_connect() is specified\nwhen connecting to mysqld, affected rows is instead the number of rows matched\nby the WHERE clause.\n\nFor REPLACE, deleted rows are also counted. So, if REPLACE deletes a row and\nadds a new row, ROW_COUNT() returns 2.\n\nFor INSERT ... ON DUPLICATE KEY, updated rows are counted twice. So, if INSERT\nadds a new rows and modifies another row, ROW_COUNT() returns 3.\n\nROW_COUNT() does not take into account rows that are not directly\ndeleted/updated by the last statement. This means that rows deleted by foreign\nkeys or triggers are not counted.\n\nWarning: You can use ROW_COUNT() with prepared statements, but you need to\ncall it after EXECUTE, not after DEALLOCATE PREPARE, because the row count for\nallocate prepare is always 0.\n\nWarning: When used after a CALL statement, this function returns the number of\nrows affected by the last statement in the procedure, not by the whole\nprocedure.\n\nWarning: After INSERT DELAYED, ROW_COUNT() returns the number of the rows you\ntried to insert, not the number of the successful writes.\n\nThis information can also be found in the diagnostics area.\n\nStatements using the ROW_COUNT() function are not safe for statement-based\nreplication.\n\nExamples\n--------\n\nCREATE TABLE t (A INT);\n ... +[ROW_NUMBER] declaration= category=Window Functions -description=ROW_NUMBER() is a window function that displays the number\nof a given row, starting at one and following the ORDER BY\nsequence of the window function, with identical values\nreceiving different row numbers. It is similar to the RANK()\nand DENSE_RANK() functions except that in that function,\nidentical values will receive the same rank for each result.\n \n\nThe distinction between DENSE_RANK(), RANK() and\nROW_NUMBER():\n \nCREATE TABLE student(course VARCHAR(10), mark int, name\nvarchar(10));\n \nINSERT INTO student VALUES \n (''Maths'', 60, ''Thulile''),\n (''Maths'', 60, ''Pritha''),\n (''Maths'', 70, ''Voitto''),\n (''Maths'', 55, ''Chun''),\n (''Biology'', 60, ''Bilal''),\n (''Biology'', 70, ''Roger'');\n \nSELECT \n RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS\nrank, \n DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC)\nAS dense_rank, \n ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC)\nAS row_num, \n course, mark, name \nFROM student ORDER BY course, mark DESC;\n \n+------+------------+---------+---------+------+---------+\n| rank | dense_rank | row_num | course | mark | name |\n+------+------------+---------+---------+------+---------+\n| 1 | 1 | 1 | Biology | 70 | Roger |\n| 2 | 2 | 2 | Biology | 60 | Bilal |\n| 1 | 1 | 1 | Maths | 70 | Voitto |\n| 2 | 2 | 2 | Maths | 60 | Thulile |\n| 2 | 2 | 3 | Maths | 60 | Pritha |\n| 4 | 3 | 4 | Maths | 55 | Chun |\n+------+------------+---------+---------+------+---------+ -[ROW_NUMBER2] -name=ROW_NUMBER -declaration=[ PARTITION BY partition_expression ] [ ORDER BY order_list ] -category=Window Functions -description=ROW_NUMBER() is a window function that displays the number\nof a given row, starting at one and following the ORDER BY\nsequence of the window function, with identical values\nreceiving different row numbers. It is similar to the RANK()\nand DENSE_RANK() functions except that in that function,\nidentical values will receive the same rank for each result.\n \n\nThe distinction between DENSE_RANK(), RANK() and\nROW_NUMBER():\n \nCREATE TABLE student(course VARCHAR(10), mark int, name\nvarchar(10));\n \nINSERT INTO student VALUES \n (''Maths'', 60, ''Thulile''),\n (''Maths'', 60, ''Pritha''),\n (''Maths'', 70, ''Voitto''),\n (''Maths'', 55, ''Chun''),\n (''Biology'', 60, ''Bilal''),\n (''Biology'', 70, ''Roger'');\n \nSELECT \n RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS\nrank, \n DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC)\nAS dense_rank, \n ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC)\nAS row_num, \n course, mark, name \nFROM student ORDER BY course, mark DESC;\n \n+------+------------+---------+---------+------+---------+\n| rank | dense_rank | row_num | course | mark | name |\n+------+------------+---------+---------+------+---------+\n| 1 | 1 | 1 | Biology | 70 | Roger |\n| 2 | 2 | 2 | Biology | 60 | Bilal |\n| 1 | 1 | 1 | Maths | 70 | Voitto |\n| 2 | 2 | 2 | Maths | 60 | Thulile |\n| 2 | 2 | 3 | Maths | 60 | Pritha |\n| 4 | 3 | 4 | Maths | 55 | Chun |\n+------+------------+---------+---------+------+---------+ +description=ROW_NUMBER() is a window function that displays the number of a given row,\nstarting at one and following the ORDER BY sequence of the window function,\nwith identical values receiving different row numbers. It is similar to the\nRANK() and DENSE_RANK() functions except that in that function, identical\nvalues will receive the same rank for each result.\n\nExamples\n--------\n\nThe distinction between DENSE_RANK(), RANK() and ROW_NUMBER():\n\nCREATE TABLE student(course VARCHAR(10), mark int, name varchar(10));\n\nINSERT INTO student VALUES \n ('Maths', 60, 'Thulile'),\n ('Maths', 60, 'Pritha'),\n ('Maths', 70, 'Voitto'),\n ('Maths', 55, 'Chun'),\n ('Biology', 60, 'Bilal'),\n ('Biology', 70, 'Roger');\n\nSELECT \n RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS rank,\n DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS dense_rank,\n ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC) AS row_num,\n course, mark, name\nFROM student ORDER BY course, mark DESC;\n+------+------------+---------+---------+------+---------+\n| rank | dense_rank | row_num | course | mark | name |\n+------+------------+---------+---------+------+---------+\n| 1 | 1 | 1 | Biology | 70 | Roger |\n| 2 | 2 | 2 | Biology | 60 | Bilal |\n| 1 | 1 | 1 | Maths | 70 | Voitto |\n| 2 | 2 | 2 | Maths | 60 | Thulile |\n| 2 | 2 | 3 | Maths | 60 | Pritha |\n| 4 | 3 | 4 | Maths | 55 | Chun |\n+------+------------+---------+---------+------+---------+\n\nURL: https://mariadb.com/kb/en/row_number/ [RPAD] declaration=str, len [, padstr] category=String Functions -description=Returns the string str, right-padded with the string padstr\nto a\nlength of len characters. If str is longer than len, the\nreturn value\nis shortened to len characters. If padstr is omitted, the\nRPAD function pads spaces.\n \nPrior to MariaDB 10.3.1, the padstr parameter was mandatory.\n \nReturns NULL if given a NULL argument. If the result is\nempty (a length of zero), returns either an empty string,\nor, from MariaDB 10.3.6 with SQL_MODE=Oracle, NULL.\n \nThe Oracle mode version of the function can be accessed\noutside of Oracle mode by using RPAD_ORACLE as the function\nname.\n \n\nSELECT RPAD(''hello'',10,''.'');\n+----------------------+\n| RPAD(''hello'',10,''.'') |\n+----------------------+\n| hello..... |\n+----------------------+\n \nSELECT RPAD(''hello'',2,''.'');\n+---------------------+\n| RPAD(''hello'',2,''.'') |\n+---------------------+\n| he |\n+---------------------+\n \nFrom MariaDB 10.3.1, with the pad string defaulting to\nspace.\n \nSELECT RPAD(''hello'',30);\n+--------------------------------+\n| RPAD(''hello'',30) |\n+--------------------------------+\n| hello |\n+--------------------------------+\n \nOracle mode version from MariaDB 10.3.6:\n \nSELECT RPAD('''',0),RPAD_ORACLE('''',0);\n+------------+-------------------+\n| RPAD('''',0) | RPAD_ORACLE('''',0) |\n+------------+-------------------+\n| | NULL |\n+------------+-------------------+ +description=Returns the string str, right-padded with the string padstr to a length of len\ncharacters. If str is longer than len, the return value is shortened to len\ncharacters. If padstr is omitted, the RPAD function pads spaces.\n\nPrior to MariaDB 10.3.1, the padstr parameter was mandatory.\n\nReturns NULL if given a NULL argument. If the result is empty (a length of\nzero), returns either an empty string, or, from MariaDB 10.3.6 with\nSQL_MODE=Oracle, NULL.\n\nThe Oracle mode version of the function can be accessed outside of Oracle mode\nby using RPAD_ORACLE as the function name.\n\nExamples\n--------\n\nSELECT RPAD('hello',10,'.');\n+----------------------+\n| RPAD('hello',10,'.') |\n+----------------------+\n| hello..... |\n+----------------------+\n\nSELECT RPAD('hello',2,'.');\n+---------------------+\n| RPAD('hello',2,'.') |\n+---------------------+\n| he |\n+---------------------+\n\nFrom MariaDB 10.3.1, with the pad string defaulting to space.\n\nSELECT RPAD('hello',30);\n+--------------------------------+\n| RPAD('hello',30) |\n+--------------------------------+\n| hello |\n+--------------------------------+\n\nOracle mode version from MariaDB 10.3.6:\n\nSELECT RPAD('',0),RPAD_ORACLE('',0);\n+------------+-------------------+\n| RPAD('',0) | RPAD_ORACLE('',0) |\n+------------+-------------------+\n| | NULL |\n+------------+-------------------+\n\nURL: https://mariadb.com/kb/en/rpad/ [RTRIM] declaration=str category=String Functions -description=Returns the string str with trailing space characters\nremoved.\n \nReturns NULL if given a NULL argument. If the result is\nempty, returns either an empty string, or, from MariaDB\n10.3.6 with SQL_MODE=Oracle, NULL.\n \nThe Oracle mode version of the function can be accessed\noutside of Oracle mode by using RTRIM_ORACLE as the function\nname.\n \n\nSELECT QUOTE(RTRIM(''MariaDB ''));\n+-----------------------------+\n| QUOTE(RTRIM(''MariaDB '')) |\n+-----------------------------+\n| ''MariaDB'' |\n+-----------------------------+\n \nOracle mode version from MariaDB 10.3.6:\n \nSELECT RTRIM(''''),RTRIM_ORACLE('''');\n+-----------+------------------+\n| RTRIM('''') | RTRIM_ORACLE('''') |\n+-----------+------------------+\n| | NULL |\n+-----------+------------------+ +description=Returns the string str with trailing space characters removed.\n\nReturns NULL if given a NULL argument. If the result is empty, returns either\nan empty string, or, from MariaDB 10.3.6 with SQL_MODE=Oracle, NULL.\n\nThe Oracle mode version of the function can be accessed outside of Oracle mode\nby using RTRIM_ORACLE as the function name.\n\nExamples\n--------\n\nSELECT QUOTE(RTRIM('MariaDB '));\n+-----------------------------+\n| QUOTE(RTRIM('MariaDB ')) |\n+-----------------------------+\n| 'MariaDB' |\n+-----------------------------+\n\nOracle mode version from MariaDB 10.3.6:\n\nSELECT RTRIM(''),RTRIM_ORACLE('');\n+-----------+------------------+\n| RTRIM('') | RTRIM_ORACLE('') |\n+-----------+------------------+\n| | NULL |\n+-----------+------------------+\n\nURL: https://mariadb.com/kb/en/rtrim/ [SCHEMA] declaration= category=Information Functions -description=This function is a synonym for DATABASE(). +description=This function is a synonym for DATABASE().\n\nURL: https://mariadb.com/kb/en/schema/ [SECOND] declaration=time category=Date and Time Functions -description=Returns the second for a given time (which can include\nmicroseconds), in the range 0 to 59, or NULL if not given a\nvalid time value.\n \n\nSELECT SECOND(''10:05:03'');\n+--------------------+\n| SECOND(''10:05:03'') |\n+--------------------+\n| 3 |\n+--------------------+\n \nSELECT SECOND(''10:05:01.999999'');\n+---------------------------+\n| SECOND(''10:05:01.999999'') |\n+---------------------------+\n| 1 |\n+---------------------------+ +description=Returns the second for a given time (which can include microseconds), in the\nrange 0 to 59, or NULL if not given a valid time value.\n\nExamples\n--------\n\nSELECT SECOND('10:05:03');\n+--------------------+\n| SECOND('10:05:03') |\n+--------------------+\n| 3 |\n+--------------------+\n\nSELECT SECOND('10:05:01.999999');\n+---------------------------+\n| SECOND('10:05:01.999999') |\n+---------------------------+\n| 1 |\n+---------------------------+\n\nURL: https://mariadb.com/kb/en/second/ [SEC_TO_TIME] declaration=seconds category=Date and Time Functions -description=Returns the seconds argument, converted to hours, minutes,\nand\nseconds, as a TIME value. The range of the result is\nconstrained to\nthat of the TIME data type. A warning occurs if the argument\ncorresponds to a value outside that range.\n \nThe time will be returned in the format hh:mm:ss, or hhmmss\nif used in a numeric calculation.\n \n\nSELECT SEC_TO_TIME(12414);\n+--------------------+\n| SEC_TO_TIME(12414) |\n+--------------------+\n| 03:26:54 |\n+--------------------+\n \nSELECT SEC_TO_TIME(12414)+0;\n \n+----------------------+\n| SEC_TO_TIME(12414)+0 |\n+----------------------+\n| 32654 |\n+----------------------+\n \nSELECT SEC_TO_TIME(9999999);\n+----------------------+\n| SEC_TO_TIME(9999999) |\n+----------------------+\n| 838:59:59 |\n+----------------------+\n1 row in set, 1 warning (0.00 sec)\n \nSHOW WARNINGS;\n \n+---------+------+-------------------------------------------+\n| Level | Code | Message |\n+---------+------+-------------------------------------------+\n| Warning | 1292 | Truncated incorrect time value:\n''9999999'' |\n+---------+------+-------------------------------------------+ +description=Returns the seconds argument, converted to hours, minutes, and seconds, as a\nTIME value. The range of the result is constrained to that of the TIME data\ntype. A warning occurs if the argument corresponds to a value outside that\nrange.\n\nThe time will be returned in the format hh:mm:ss, or hhmmss if used in a\nnumeric calculation.\n\nExamples\n--------\n\nSELECT SEC_TO_TIME(12414);\n+--------------------+\n| SEC_TO_TIME(12414) |\n+--------------------+\n| 03:26:54 |\n+--------------------+\n\nSELECT SEC_TO_TIME(12414)+0;\n+----------------------+\n| SEC_TO_TIME(12414)+0 |\n+----------------------+\n| 32654 |\n+----------------------+\n\nSELECT SEC_TO_TIME(9999999);\n+----------------------+\n| SEC_TO_TIME(9999999) |\n+----------------------+\n| 838:59:59 |\n+----------------------+\n1 row in set, 1 warning (0.00 sec)\n\nSHOW WARNINGS;\n+---------+------+-------------------------------------------+\n| Level | Code | Message |\n+---------+------+-------------------------------------------+\n| Warning | 1292 | Truncated incorrect time value: '9999999' |\n+---------+------+-------------------------------------------+\n\nURL: https://mariadb.com/kb/en/sec_to_time/ [SESSION_USER] declaration= category=Information Functions -description=SESSION_USER() is a synonym for USER(). +description=SESSION_USER() is a synonym for USER().\n\nURL: https://mariadb.com/kb/en/session_user/ +[SETVAL] +declaration=sequence_name, next_value, [is_used, [round]] +category=Sequences +description=Set the next value to be returned for a SEQUENCE.\n\nThis function is compatible with PostgreSQL syntax, extended with the round\nargument.\n\nIf the is_used argument is not given or is 1 or true, then the next used value\nwill one after the given value. If is_used is 0 or false then the next\ngenerated value will be the given value.\n\nIf round is used then it will set the round value (or the internal cycle\ncount, starting at zero) for the sequence. If round is not used, it's assumed\nto be 0.\n\nnext_value must be an integer literal.\n\nFor SEQUENCE tables defined with CYCLE (see CREATE SEQUENCE) one should use\nboth next_value and round to define the next value. In this case the current\nsequence value is defined to be round, next_value.\n\nThe result returned by SETVAL() is next_value or NULL if the given next_value\nand round is smaller than the current value.\n\nSETVAL() will not set the SEQUENCE value to a something that is less than its\ncurrent value. This is needed to ensure that SETVAL() is replication safe. If\nyou want to set the SEQUENCE to a smaller number use ALTER SEQUENCE.\n\nIf CYCLE is used, first round and then next_value are compared to see if the\nvalue is bigger than the current value.\n\nInternally, in the MariaDB server, SETVAL() is used to inform slaves that a\nSEQUENCE has changed value. The slave may get SETVAL() statements out of\norder, but this is ok as only the biggest one will have an effect.\n\nSETVAL requires the INSERT privilege.\n\nExamples\n--------\n\nSELECT setval(foo, 42); -- Next nextval will return 43\nSELECT setval(foo, 42, true); -- Same as above\nSELECT setval(foo, 42, false); -- Next nextval will return 42\n\nSETVAL setting higher and lower values on a sequence with an increment of 10:\n\nSELECT NEXTVAL(s);\n+------------+\n| NEXTVAL(s) |\n+------------+\n| 50 |\n+------------+\n ... +[SFORMAT] +declaration="The answer is {}.", 42 +category=String Functions +description=+----------------------------------+\n| SFORMAT("The answer is {}.", 42) |\n+----------------------------------+\n| The answer is 42. |\n+----------------------------------+\n\nCREATE TABLE test_sformat(mdb_release char(6), mdev int, feature char(20));\n\nINSERT INTO test_sformat VALUES('10.7.0', 25015, 'Python style sformat'), \n ('10.7.0', 4958, 'UUID');\n\nSELECT * FROM test_sformat;\n+-------------+-------+----------------------+\n| mdb_release | mdev | feature |\n+-------------+-------+----------------------+\n| 10.7.0 | 25015 | Python style sformat |\n| 10.7.0 | 4958 | UUID |\n+-------------+-------+----------------------+\n\nSELECT SFORMAT('MariaDB Server {} has a preview for MDEV-{} which is about\n{}', \n mdb_release, mdev, feature) AS 'Preview Release Examples'\n FROM test_sformat;\n+------------------------------------------------------------------------------\n---------+\n| Preview Release Examples \n |\n+------------------------------------------------------------------------------\n---------+\n| MariaDB Server 10.7.0 has a preview for MDEV-25015 which is about Python\nstyle sformat |\n| MariaDB Server 10.7.0 has a preview for MDEV-4958 which is about UUID \n |\n+------------------------------------------------------------------------------\n---------+\n\nURL: https://mariadb.com/kb/en/sformat/ [SHA1] declaration=str category=Encryption Functions -description=Calculates an SHA-1 160-bit checksum for the string str, as\ndescribed in\nRFC 3174 (Secure Hash Algorithm).\n \nThe value is returned as a string of 40 hex digits, or NULL\nif the argument was NULL. As of MariaDB 5.5, the return\nvalue is a nonbinary string in the connection character set\nand collation, determined by the values of the\ncharacter_set_connection and collation_connection system\nvariables. Before 5.5, the return value was a binary string.\n \n\nSELECT SHA1(''some boring text'');\n+------------------------------------------+\n| SHA1(''some boring text'') |\n+------------------------------------------+\n| af969fc2085b1bb6d31e517d5c456def5cdd7093 |\n+------------------------------------------+ +description=Calculates an SHA-1 160-bit checksum for the string str, as described in RFC\n3174 (Secure Hash Algorithm).\n\nThe value is returned as a string of 40 hex digits, or NULL if the argument\nwas NULL. As of MariaDB 5.5, the return value is a nonbinary string in the\nconnection character set and collation, determined by the values of the\ncharacter_set_connection and collation_connection system variables. Before\n5.5, the return value was a binary string.\n\nExamples\n--------\n\nSELECT SHA1('some boring text');\n+------------------------------------------+\n| SHA1('some boring text') |\n+------------------------------------------+\n| af969fc2085b1bb6d31e517d5c456def5cdd7093 |\n+------------------------------------------+\n\nURL: https://mariadb.com/kb/en/sha1/ [SHA2] declaration=str,hash_len category=Encryption Functions -description=Given a string str, calculates an SHA-2 checksum, which is\nconsidered more cryptographically secure than its SHA-1\nequivalent. The SHA-2 family includes SHA-224, SHA-256,\nSHA-384, and SHA-512, and the hash_len must correspond to\none of these, i.e. 224, 256, 384 or 512. 0 is equivalent to\n256.\n \nThe return value is a nonbinary string in the connection\ncharacter set and collation, determined by the values of the\ncharacter_set_connection and collation_connection system\nvariables. \n \nNULL is returned if the hash length is not valid, or the\nstring str is NULL.\n \nSHA2 will only work if MariaDB was has been configured with\nTLS support. \n \n\nSELECT SHA2(''Maria'',224);\n+----------------------------------------------------------+\n| SHA2(''Maria'',224) |\n+----------------------------------------------------------+\n| 6cc67add32286412efcab9d0e1675a43a5c2ef3cec8879f81516ff83 |\n+----------------------------------------------------------+\n \nSELECT SHA2(''Maria'',256);\n+------------------------------------------------------------------+\n| SHA2(''Maria'',256) |\n+------------------------------------------------------------------+\n|\n9ff18ebe7449349f358e3af0b57cf7a032c1c6b2272cb2656ff85eb112232f16\n|\n+------------------------------------------------------------------+\n \nSELECT SHA2(''Maria'',0);\n+------------------------------------------------------------------+\n| SHA2(''Maria'',0) |\n+------------------------------------------------------------------+\n|\n9ff18ebe7449349f358e3af0b57cf7a032c1c6b2272cb2656ff85eb112232f16\n|\n+------------------------------------------------------------------+ +description=Given a string str, calculates an SHA-2 checksum, which is considered more\ncryptographically secure than its SHA-1 equivalent. The SHA-2 family includes\nSHA-224, SHA-256, SHA-384, and SHA-512, and the hash_len must correspond to\none of these, i.e. 224, 256, 384 or 512. 0 is equivalent to 256.\n\nThe return value is a nonbinary string in the connection character set and\ncollation, determined by the values of the character_set_connection and\ncollation_connection system variables.\n\nNULL is returned if the hash length is not valid, or the string str is NULL.\n\nSHA2 will only work if MariaDB was has been configured with TLS support.\n\nExamples\n--------\n\nSELECT SHA2('Maria',224);\n+----------------------------------------------------------+\n| SHA2('Maria',224) |\n+----------------------------------------------------------+\n| 6cc67add32286412efcab9d0e1675a43a5c2ef3cec8879f81516ff83 |\n+----------------------------------------------------------+\n\nSELECT SHA2('Maria',256);\n+------------------------------------------------------------------+\n| SHA2('Maria',256) |\n+------------------------------------------------------------------+\n| 9ff18ebe7449349f358e3af0b57cf7a032c1c6b2272cb2656ff85eb112232f16 |\n+------------------------------------------------------------------+\n\nSELECT SHA2('Maria',0);\n+------------------------------------------------------------------+\n| SHA2('Maria',0) |\n+------------------------------------------------------------------+\n| 9ff18ebe7449349f358e3af0b57cf7a032c1c6b2272cb2656ff85eb112232f16 |\n+------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/sha2/ [SIGN] declaration=X category=Numeric Functions -description=Returns the sign of the argument as -1, 0, or 1, depending\non whether\nX is negative, zero, or positive.\n \n\nSELECT SIGN(-32);\n+-----------+\n| SIGN(-32) |\n+-----------+\n| -1 |\n+-----------+\n \nSELECT SIGN(0);\n+---------+\n| SIGN(0) |\n+---------+\n| 0 |\n+---------+\n \nSELECT SIGN(234);\n+-----------+\n| SIGN(234) |\n+-----------+\n| 1 |\n+-----------+ +description=Returns the sign of the argument as -1, 0, or 1, depending on whether X is\nnegative, zero, or positive.\n\nExamples\n--------\n\nSELECT SIGN(-32);\n+-----------+\n| SIGN(-32) |\n+-----------+\n| -1 |\n+-----------+\n\nSELECT SIGN(0);\n+---------+\n| SIGN(0) |\n+---------+\n| 0 |\n+---------+\n\nSELECT SIGN(234);\n+-----------+\n| SIGN(234) |\n+-----------+\n| 1 |\n+-----------+\n\nURL: https://mariadb.com/kb/en/sign/ [SIN] declaration=X category=Numeric Functions -description=Returns the sine of X, where X is given in radians.\n \n\nSELECT SIN(1.5707963267948966);\n+-------------------------+\n| SIN(1.5707963267948966) |\n+-------------------------+\n| 1 |\n+-------------------------+\n \nSELECT SIN(PI());\n+----------------------+\n| SIN(PI()) |\n+----------------------+\n| 1.22460635382238e-16 |\n+----------------------+\n \nSELECT ROUND(SIN(PI()));\n+------------------+\n| ROUND(SIN(PI())) |\n+------------------+\n| 0 |\n+------------------+ +description=Returns the sine of X, where X is given in radians.\n\nExamples\n--------\n\nSELECT SIN(1.5707963267948966);\n+-------------------------+\n| SIN(1.5707963267948966) |\n+-------------------------+\n| 1 |\n+-------------------------+\n\nSELECT SIN(PI());\n+----------------------+\n| SIN(PI()) |\n+----------------------+\n| 1.22460635382238e-16 |\n+----------------------+\n\nSELECT ROUND(SIN(PI()));\n+------------------+\n| ROUND(SIN(PI())) |\n+------------------+\n| 0 |\n+------------------+\n\nURL: https://mariadb.com/kb/en/sin/ [SLEEP] declaration=duration category=Miscellaneous Functions -description=Sleeps (pauses) for the number of seconds given by the\nduration argument, then\nreturns 0. If SLEEP() is interrupted, it\nreturns 1. The duration may have a fractional part given in\nmicroseconds.\n \nStatements using the SLEEP() function are not safe for\nreplication.\n \nExample\n \nSELECT SLEEP(5.5);\n+------------+\n| SLEEP(5.5) |\n+------------+\n| 0 |\n+------------+\n1 row in set (5.50 sec) +description=Sleeps (pauses) for the number of seconds given by the duration argument, then\nreturns 0. If SLEEP() is interrupted, it returns 1. The duration may have a\nfractional part given in microseconds.\n\nStatements using the SLEEP() function are not safe for statement-based\nreplication.\n\nExample\n-------\n\nSELECT SLEEP(5.5);\n+------------+\n| SLEEP(5.5) |\n+------------+\n| 0 |\n+------------+\n1 row in set (5.50 sec)\n\nURL: https://mariadb.com/kb/en/sleep/ +[SMALLINT] +declaration=M +category=Data Types +description=A small integer. The signed range is -32768 to 32767. The unsigned range is 0\nto 65535.\n\nIf a column has been set to ZEROFILL, all values will be prepended by zeros so\nthat the SMALLINT value contains a number of M digits.\n\nNote: If the ZEROFILL attribute has been specified, the column will\nautomatically become UNSIGNED.\n\nINT2 is a synonym for SMALLINT.\n\nFor more details on the attributes, see Numeric Data Type Overview.\n\nExamples\n--------\n\nCREATE TABLE smallints (a SMALLINT,b SMALLINT UNSIGNED,c SMALLINT ZEROFILL);\n\nWith strict_mode set, the default from MariaDB 10.2.4:\n\nINSERT INTO smallints VALUES (-10,-10,-10);\nERROR 1264 (22003): Out of range value for column 'b' at row 1\n\nINSERT INTO smallints VALUES (-10,10,-10);\nERROR 1264 (22003): Out of range value for column 'c' at row 1\n\nINSERT INTO smallints VALUES (-10,10,10);\n\nINSERT INTO smallints VALUES (32768,32768,32768);\nERROR 1264 (22003): Out of range value for column 'a' at row 1\n\nINSERT INTO smallints VALUES (32767,32768,32768);\n\nSELECT * FROM smallints;\n+-------+-------+-------+\n| a | b | c |\n+-------+-------+-------+\n| -10 | 10 | 00010 |\n| 32767 | 32768 | 32768 |\n+-------+-------+-------+\n\nWith strict_mode unset, the default until MariaDB 10.2.3:\n\nINSERT INTO smallints VALUES (-10,-10,-10);\nQuery OK, 1 row affected, 2 warnings (0.09 sec)\nWarning (Code 1264): Out of range value for column 'b' at row 1\nWarning (Code 1264): Out of range value for column 'c' at row 1\n\nINSERT INTO smallints VALUES (-10,10,-10);\nQuery OK, 1 row affected, 1 warning (0.08 sec)\n ... [SOUNDEX] declaration=str category=String Functions -description=Returns a soundex string from str. Two strings that sound\nalmost the\nsame should have identical soundex strings. A standard\nsoundex string is four\ncharacters long, but the SOUNDEX() function returns an\narbitrarily long\nstring. You can use SUBSTRING() on the result to get a\nstandard soundex\nstring. All non-alphabetic characters in str are ignored.\nAll\ninternational alphabetic characters outside the A-Z range\nare treated as\nvowels.\n \nImportant: When using SOUNDEX(), you should be aware of the\nfollowing limitations:\nThis function, as currently implemented, is intended to work\nwell with\n strings that are in the English language only. Strings in\nother languages may\n not produce reliable results.\n \n\nSOUNDEX(''Hello'');\n+------------------+\n| SOUNDEX(''Hello'') |\n+------------------+\n| H400 |\n+------------------+\n \nSELECT SOUNDEX(''MariaDB'');\n+--------------------+\n| SOUNDEX(''MariaDB'') |\n+--------------------+\n| M631 |\n+--------------------+\n \nSELECT SOUNDEX(''Knowledgebase'');\n+--------------------------+\n| SOUNDEX(''Knowledgebase'') |\n+--------------------------+\n| K543212 |\n+--------------------------+\n \nSELECT givenname, surname FROM users WHERE\nSOUNDEX(givenname) = SOUNDEX("robert");\n+-----------+---------+\n| givenname | surname |\n+-----------+---------+\n| Roberto | Castro |\n+-----------+---------+ +description=Returns a soundex string from str. Two strings that sound almost the same\nshould have identical soundex strings. A standard soundex string is four\ncharacters long, but the SOUNDEX() function returns an arbitrarily long\nstring. You can use SUBSTRING() on the result to get a standard soundex\nstring. All non-alphabetic characters in str are ignored. All international\nalphabetic characters outside the A-Z range are treated as vowels.\n\nImportant: When using SOUNDEX(), you should be aware of the following details:\n\n* This function, as currently implemented, is intended to work well with\n strings that are in the English language only. Strings in other languages may\n not produce reasonable results.\n\n* This function implements the original Soundex algorithm, not the more\npopular enhanced version (also described by D. Knuth). The difference is that\noriginal version discards vowels first and duplicates second, whereas the\nenhanced version discards duplicates first and vowels second.\n\nExamples\n--------\n\nSOUNDEX('Hello');\n+------------------+\n| SOUNDEX('Hello') |\n+------------------+\n| H400 |\n+------------------+\n\nSELECT SOUNDEX('MariaDB');\n+--------------------+\n| SOUNDEX('MariaDB') |\n+--------------------+\n| M631 |\n+--------------------+\n\nSELECT SOUNDEX('Knowledgebase');\n+--------------------------+\n| SOUNDEX('Knowledgebase') |\n+--------------------------+\n| K543212 |\n+--------------------------+\n\nSELECT givenname, surname FROM users WHERE SOUNDEX(givenname) =\nSOUNDEX("robert");\n+-----------+---------+\n| givenname | surname |\n+-----------+---------+\n| Roberto | Castro |\n+-----------+---------+\n\nURL: https://mariadb.com/kb/en/soundex/ [SPACE] declaration=N category=String Functions -description=Returns a string consisting of N space characters. If N is\nNULL, returns NULL.\n \n\nSELECT QUOTE(SPACE(6));\n+-----------------+\n| QUOTE(SPACE(6)) |\n+-----------------+\n| '' '' |\n+-----------------+ +description=Returns a string consisting of N space characters. If N is NULL, returns NULL.\n\nExamples\n--------\n\nSELECT QUOTE(SPACE(6));\n+-----------------+\n| QUOTE(SPACE(6)) |\n+-----------------+\n| ' ' |\n+-----------------+\n\nURL: https://mariadb.com/kb/en/space/ [SPIDER_BG_DIRECT_SQL] -declaration=''sql'', ''tmp_table_list'', ''parameters'' +declaration='sql', 'tmp_table_list', 'parameters' category=Spider Functions -description=Executes the given SQL statement in the background on the\nremote server, as defined in the parameters listing. If the\nquery returns a result-set, it sttores the results in the\ngiven temporary table. When the given SQL statement executes\nsuccessfully, this function returns the number of called\nUDF''s. It returns 0 when the given SQL statement fails.\n \nThis function is a UDF installed with the Spider storage\nengine.\n \n\nSELECT SPIDER_BG_DIRECT_SQL(''SELECT * FROM example_table'',\n'''', \n ''srv "node1", port "8607"'') AS "Direct Query";\n+--------------+\n| Direct Query | \n+--------------+\n| 1 |\n+--------------+\n \nParameters\n \nerror_rw_mode\n \nDescription: Returns empty results on network error.\n0 : Return error on getting network error.\n1: Return 0 records on getting network error.\n \nDefault Table Value: 0\nDSN Parameter Name: erwm +description=Executes the given SQL statement in the background on the remote server, as\ndefined in the parameters listing. If the query returns a result-set, it\nsttores the results in the given temporary table. When the given SQL statement\nexecutes successfully, this function returns the number of called UDF's. It\nreturns 0 when the given SQL statement fails.\n\nThis function is a UDF installed with the Spider storage engine.\n\nExamples\n--------\n\nSELECT SPIDER_BG_DIRECT_SQL('SELECT * FROM example_table', '', \n 'srv "node1", port "8607"') AS "Direct Query";\n+--------------+\n| Direct Query | \n+--------------+\n| 1 |\n+--------------+\n\nParameters\n----------\n\nerror_rw_mode\n-------------\n\n* Description: Returns empty results on network error.\n0 : Return error on getting network error.\n1: Return 0 records on getting network error.\n\n* Default Table Value: 0\n* DSN Parameter Name: erwm\n\nURL: https://mariadb.com/kb/en/spider_bg_direct_sql/ [SPIDER_COPY_TABLES] declaration=spider_table_name, source_link_id, destination_link_id_list [,parameters] category=Spider Functions -description=A UDF installed with the Spider Storage Engine, this\nfunction copies table data from source_link_id to\ndestination_link_id_list. The service does not need to be\nstopped in order to copy.\n \nIf the Spider table is partitioned, the name must be of the\nformat table_name#P#partition_name. The partition name can\nbe viewed in the mysql.spider_tables table, for example:\n \nSELECT table_name FROM mysql.spider_tables;\n+-------------+\n| table_name |\n+-------------+\n| spt_a#P#pt1 |\n| spt_a#P#pt2 |\n| spt_a#P#pt3 |\n+-------------+\n \nReturns 1 if the data was copied successfully, or 0 if\ncopying the data failed. +description=A UDF installed with the Spider Storage Engine, this function copies table\ndata from source_link_id to destination_link_id_list. The service does not\nneed to be stopped in order to copy.\n\nIf the Spider table is partitioned, the name must be of the format\ntable_name#P#partition_name. The partition name can be viewed in the\nmysql.spider_tables table, for example:\n\nSELECT table_name FROM mysql.spider_tables;\n+-------------+\n| table_name |\n+-------------+\n| spt_a#P#pt1 |\n| spt_a#P#pt2 |\n| spt_a#P#pt3 |\n+-------------+\n\nReturns 1 if the data was copied successfully, or 0 if copying the data failed.\n\nURL: https://mariadb.com/kb/en/spider_copy_tables/ [SPIDER_DIRECT_SQL] -declaration=''sql'', ''tmp_table_list'', ''parameters'' +declaration='sql', 'tmp_table_list', 'parameters' category=Spider Functions -description=A UDF installed with the Spider Storage Engine, this\nfunction is used to execute the SQL string sql on the remote\nserver, as defined in parameters. If any resultsets are\nreturned, they are stored in the tmp_table_list.\n \nThe function returns 1 if the SQL executes successfully, or\n0 if it fails.\n \n\nSELECT SPIDER_DIRECT_SQL(''SELECT * FROM s'', '''', ''srv\n"node1", port "8607"'');\n+----------------------------------------------------------------------+\n| SPIDER_DIRECT_SQL(''SELECT * FROM s'', '''', ''srv\n"node1", port "8607"'') |\n+----------------------------------------------------------------------+\n| 1 |\n+----------------------------------------------------------------------+ +description=A UDF installed with the Spider Storage Engine, this function is used to\nexecute the SQL string sql on the remote server, as defined in parameters. If\nany resultsets are returned, they are stored in the tmp_table_list.\n\nThe function returns 1 if the SQL executes successfully, or 0 if it fails.\n\nExamples\n--------\n\nSELECT SPIDER_DIRECT_SQL('SELECT * FROM s', '', 'srv "node1", port "8607"');\n+----------------------------------------------------------------------+\n| SPIDER_DIRECT_SQL('SELECT * FROM s', '', 'srv "node1", port "8607"') |\n+----------------------------------------------------------------------+\n| 1 |\n+----------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/spider_direct_sql/ [SPIDER_FLUSH_TABLE_MON_CACHE] declaration= category=Spider Functions -description=A UDF installed with the Spider Storage Engine, this\nfunction is used for refreshing monitoring server\ninformation. It returns a value of 1.\n \n\nSELECT SPIDER_FLUSH_TABLE_MON_CACHE();\n+--------------------------------+\n| SPIDER_FLUSH_TABLE_MON_CACHE() |\n+--------------------------------+\n| 1 |\n+--------------------------------+ +description=A UDF installed with the Spider Storage Engine, this function is used for\nrefreshing monitoring server information. It returns a value of 1.\n\nExamples\n--------\n\nSELECT SPIDER_FLUSH_TABLE_MON_CACHE();\n+--------------------------------+\n| SPIDER_FLUSH_TABLE_MON_CACHE() |\n+--------------------------------+\n| 1 |\n+--------------------------------+\n\nURL: https://mariadb.com/kb/en/spider_flush_table_mon_cache/ [SQRT] declaration=X category=Numeric Functions -description=Returns the square root of X. If X is negative, NULL is\nreturned.\n \n\nSELECT SQRT(4);\n+---------+\n| SQRT(4) |\n+---------+\n| 2 |\n+---------+\n \nSELECT SQRT(20);\n+------------------+\n| SQRT(20) |\n+------------------+\n| 4.47213595499958 |\n+------------------+\n \nSELECT SQRT(-16);\n+-----------+\n| SQRT(-16) |\n+-----------+\n| NULL |\n+-----------+\n \nSELECT SQRT(1764);\n+------------+\n| SQRT(1764) |\n+------------+\n| 42 |\n+------------+ +description=Returns the square root of X. If X is negative, NULL is returned.\n\nExamples\n--------\n\nSELECT SQRT(4);\n+---------+\n| SQRT(4) |\n+---------+\n| 2 |\n+---------+\n\nSELECT SQRT(20);\n+------------------+\n| SQRT(20) |\n+------------------+\n| 4.47213595499958 |\n+------------------+\n\nSELECT SQRT(-16);\n+-----------+\n| SQRT(-16) |\n+-----------+\n| NULL |\n+-----------+\n\nSELECT SQRT(1764);\n+------------+\n| SQRT(1764) |\n+------------+\n| 42 |\n+------------+\n\nURL: https://mariadb.com/kb/en/sqrt/ +[STD] +declaration=expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the population standard deviation of expr. This is an extension to\nstandard SQL. The standard SQL function STDDEV_POP() can be used instead.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nSTD() can be used as a window function.\n\nThis function returns NULL if there were no matching rows.\n\nExamples\n--------\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n ('Chun', 'SQL', 75), ('Chun', 'Tuning', 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87);\n\nSELECT name, test, score, STDDEV_POP(score) \n OVER (PARTITION BY test) AS stddev_results FROM student_test;\n+---------+--------+-------+----------------+\n| name | test | score | stddev_results |\n+---------+--------+-------+----------------+\n| Chun | SQL | 75 | 16.9466 |\n| Chun | Tuning | 73 | 24.1247 |\n| Esben | SQL | 43 | 16.9466 |\n| Esben | Tuning | 31 | 24.1247 |\n| Kaolin | SQL | 56 | 16.9466 |\n ... [STDDEV] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the population standard deviation of expr. This\nfunction is\nprovided for compatibility with Oracle. The standard SQL\nfunction\nSTDDEV_POP() can be used instead.\n \nIt is an aggregate function, and so can be used with the\nGROUP BY clause.\n \nFrom MariaDB 10.2.2, STDDEV() can be used as a window\nfunction.\n \nThis function returns NULL if there were no matching rows.\n \n\nAs an aggregate function:\n \nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n \nINSERT INTO stats VALUES \n (''a'',1),(''a'',2),(''a'',3),\n (''b'',11),(''b'',12),(''b'',20),(''b'',30),(''b'',60);\n \nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n \n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n \nAs a window function:\n \nCREATE OR REPLACE TABLE student_test (name CHAR(10), test\nCHAR(10), score TINYINT);\n \nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87);\n \nSELECT name, test, score, STDDEV_POP(score) \n OVER (PARTITION BY test) AS stddev_results FROM\nstudent_test;\n \n+---------+--------+-------+----------------+\n| name | test | score | stddev_results |\n+---------+--------+-------+----------------+\n| Chun | SQL | 75 | 16.9466 |\n| Chun | Tuning | 73 | 24.1247 |\n| Esben | SQL | 43 | 16.9466 |\n| Esben | Tuning | 31 | 24.1247 |\n| Kaolin | SQL | 56 | 16.9466 |\n| Kaolin | Tuning | 88 | 24.1247 |\n| Tatiana | SQL | 87 | 16.9466 |\n+---------+--------+-------+----------------+ +description=Returns the population standard deviation of expr. This function is provided\nfor compatibility with Oracle. The standard SQL function STDDEV_POP() can be\nused instead.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nSTDDEV() can be used as a window function.\n\nThis function returns NULL if there were no matching rows.\n\nExamples\n--------\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n ('Chun', 'SQL', 75), ('Chun', 'Tuning', 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87);\n\nSELECT name, test, score, STDDEV_POP(score) \n OVER (PARTITION BY test) AS stddev_results FROM student_test;\n+---------+--------+-------+----------------+\n| name | test | score | stddev_results |\n+---------+--------+-------+----------------+\n| Chun | SQL | 75 | 16.9466 |\n| Chun | Tuning | 73 | 24.1247 |\n| Esben | SQL | 43 | 16.9466 |\n| Esben | Tuning | 31 | 24.1247 |\n ... [STDDEV_POP] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the population standard deviation of expr (the\nsquare root of\nVAR_POP()). You can also use STD() or\nSTDDEV(), which are equivalent but not standard SQL.\n \nIt is an aggregate function, and so can be used with the\nGROUP BY clause.\n \nFrom MariaDB 10.2.2, STDDEV_POP() can be used as a window\nfunction.\n \nSTDDEV_POP() returns NULL if there were no matching rows.\n \n\nAs an aggregate function:\n \nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n \nINSERT INTO stats VALUES \n (''a'',1),(''a'',2),(''a'',3),\n (''b'',11),(''b'',12),(''b'',20),(''b'',30),(''b'',60);\n \nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n \n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n \nAs a window function:\n \nCREATE OR REPLACE TABLE student_test (name CHAR(10), test\nCHAR(10), score TINYINT);\n \nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87);\n \nSELECT name, test, score, STDDEV_POP(score) \n OVER (PARTITION BY test) AS stddev_results FROM\nstudent_test;\n \n+---------+--------+-------+----------------+\n| name | test | score | stddev_results |\n+---------+--------+-------+----------------+\n| Chun | SQL | 75 | 16.9466 |\n| Chun | Tuning | 73 | 24.1247 |\n| Esben | SQL | 43 | 16.9466 |\n| Esben | Tuning | 31 | 24.1247 |\n| Kaolin | SQL | 56 | 16.9466 |\n| Kaolin | Tuning | 88 | 24.1247 |\n| Tatiana | SQL | 87 | 16.9466 |\n+---------+--------+-------+----------------+ +description=Returns the population standard deviation of expr (the square root of\nVAR_POP()). You can also use STD() or STDDEV(), which are equivalent but not\nstandard SQL.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nSTDDEV_POP() can be used as a window function.\n\nSTDDEV_POP() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n ('Chun', 'SQL', 75), ('Chun', 'Tuning', 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87);\n\nSELECT name, test, score, STDDEV_POP(score) \n OVER (PARTITION BY test) AS stddev_results FROM student_test;\n+---------+--------+-------+----------------+\n| name | test | score | stddev_results |\n+---------+--------+-------+----------------+\n| Chun | SQL | 75 | 16.9466 |\n| Chun | Tuning | 73 | 24.1247 |\n| Esben | SQL | 43 | 16.9466 |\n| Esben | Tuning | 31 | 24.1247 |\n ... [STDDEV_SAMP] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the sample standard deviation of expr (the square\nroot of VAR_SAMP()).\n \nIt is an aggregate function, and so can be used with the\nGROUP BY clause.\n \nFrom MariaDB 10.2.2, STDDEV_SAMP() can be used as a window\nfunction.\n \nSTDDEV_SAMP() returns NULL if there were no matching rows. -[STD] -declaration=expr -category=Functions and Modifiers for Use with GROUP BY -description=Returns the population standard deviation of expr. This is\nan extension\nto standard SQL. The standard SQL function STDDEV_POP() can\nbe used instead. \n \nIt is an aggregate function, and so can be used with the\nGROUP BY clause.\n \nFrom MariaDB 10.2.2, STD() can be used as a window function.\n \nThis function returns NULL if there were no matching rows.\n \n\nAs an aggregate function:\n \nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n \nINSERT INTO stats VALUES \n (''a'',1),(''a'',2),(''a'',3),\n (''b'',11),(''b'',12),(''b'',20),(''b'',30),(''b'',60);\n \nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n \n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n \nAs a window function:\n \nCREATE OR REPLACE TABLE student_test (name CHAR(10), test\nCHAR(10), score TINYINT);\n \nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87);\n \nSELECT name, test, score, STDDEV_POP(score) \n OVER (PARTITION BY test) AS stddev_results FROM\nstudent_test;\n \n+---------+--------+-------+----------------+\n| name | test | score | stddev_results |\n+---------+--------+-------+----------------+\n| Chun | SQL | 75 | 16.9466 |\n| Chun | Tuning | 73 | 24.1247 |\n| Esben | SQL | 43 | 16.9466 |\n| Esben | Tuning | 31 | 24.1247 |\n| Kaolin | SQL | 56 | 16.9466 |\n| Kaolin | Tuning | 88 | 24.1247 |\n| Tatiana | SQL | 87 | 16.9466 |\n+---------+--------+-------+----------------+ +description=Returns the sample standard deviation of expr (the square root of VAR_SAMP()).\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nSTDDEV_SAMP() can be used as a window function.\n\nSTDDEV_SAMP() returns NULL if there were no matching rows.\n\nURL: https://mariadb.com/kb/en/stddev_samp/ [STRCMP] declaration=expr1,expr2 category=String Functions -description=STRCMP() returns 0 if the strings are the same, -1 if the\nfirst\nargument is smaller than the second according to the current\nsort order,\nand 1 otherwise.\n \n\nSELECT STRCMP(''text'', ''text2'');\n+-------------------------+\n| STRCMP(''text'', ''text2'') |\n+-------------------------+\n| -1 |\n+-------------------------+\n \nSELECT STRCMP(''text2'', ''text'');\n+-------------------------+\n| STRCMP(''text2'', ''text'') |\n+-------------------------+\n| 1 |\n+-------------------------+\n \nSELECT STRCMP(''text'', ''text'');\n+------------------------+\n| STRCMP(''text'', ''text'') |\n+------------------------+\n| 0 |\n+------------------------+ +description=STRCMP() returns 0 if the strings are the same, -1 if the first argument is\nsmaller than the second according to the current sort order, and 1 if the\nstrings are otherwise not the same. Returns NULL is either argument is NULL.\n\nExamples\n--------\n\nSELECT STRCMP('text', 'text2');\n+-------------------------+\n| STRCMP('text', 'text2') |\n+-------------------------+\n| -1 |\n+-------------------------+\n\nSELECT STRCMP('text2', 'text');\n+-------------------------+\n| STRCMP('text2', 'text') |\n+-------------------------+\n| 1 |\n+-------------------------+\n\nSELECT STRCMP('text', 'text');\n+------------------------+\n| STRCMP('text', 'text') |\n+------------------------+\n| 0 |\n+------------------------+\n\nURL: https://mariadb.com/kb/en/strcmp/ [STR_TO_DATE] declaration=str,format category=Date and Time Functions -description=This is the inverse of the DATE_FORMAT() function. It takes\na string str and a format string format. STR_TO_DATE()\nreturns a\nDATETIME value if the format string contains both date and\ntime parts, or a\nDATE or TIME value if the string contains only date or time\nparts.\n \nThe date, time, or datetime values contained in str should\nbe given in the format indicated by format. If str contains\nan illegal date, time, or datetime value, STR_TO_DATE()\nreturns NULL. An illegal value also produces a warning.\n \nThe options that can be used by STR_TO_DATE(), as well as\nits inverse DATE_FORMAT() and the FROM_UNIXTIME() function,\nare:\n \nOption | Description | \n \n%a | Short weekday name in current locale (Variable\nlc_time_names). | \n \n%b | Short form month name in current locale. For locale\nen_US this is one of:\nJan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov or Dec. | \n \n%c | Month with 1 or 2 digits. | \n \n%D | Day with English suffix ''th'', ''nd'', ''st'' or\n''rd''''. (1st, 2nd, 3rd...). | \n \n%d | Day with 2 digits. | \n \n%e | Day with 1 or 2 digits. | \n \n%f | Sub seconds 6 digits. | \n \n%H | Hour with 2 digits between 00-23. | \n \n%h | Hour with 2 digits between 01-12. | \n \n%I | Hour with 2 digits between 01-12. | \n \n%i | Minute with 2 digits. | \n \n%j | Day of the year (001-366) | \n \n%k | Hour with 1 digits between 0-23. | \n \n%l | Hour with 1 digits between 1-12. | \n \n%M | Full month name in current locale (Variable\nlc_time_names). | \n \n%m | Month with 2 digits. | \n \n%p | AM/PM according to current locale (Variable\nlc_time_names). | \n \n%r | Time in 12 hour format, followed by AM/PM. Short for\n''%I:%i:%S %p''. | \n \n%S | Seconds with 2 digits. | \n \n%s | Seconds with 2 digits. | \n \n%T | Time in 24 hour format. Short for ''%H:%i:%S''. | \n \n%U | Week number (00-53), when first day of the week is\nSunday. | \n \n%u | Week number (00-53), when first day of the week is\nMonday. | \n \n%V | Week number (01-53), when first day of the week is\nSunday. Used with %X. | \n \n%v | Week number (01-53), when first day of the week is\nMonday. Used with %x. | \n \n%W | Full weekday name in current locale (Variable\nlc_time_names). | \n \n%w | Day of the week. 0 = Sunday, 6 = Saturday. | \n \n%X | Year with 4 digits when first day of the week is\nSunday. Used with %V. | \n \n%x | Year with 4 digits when first day of the week is\nMonday. Used with %v. | \n \n%Y | Year with 4 digits. | \n \n%y | Year with 2 digits. | \n \n%# | For str_to_date(), skip all numbers. | \n \n%. | For str_to_date(), skip all punctation characters. | \n \n%@ | For str_to_date(), skip all alpha characters. | \n \n%% | A literal % character. | \n \n\nSELECT STR_TO_DATE(''Wednesday, June 2, 2014'', ''%W, %M %e,\n%Y'');\n+---------------------------------------------------------+\n| STR_TO_DATE(''Wednesday, June 2, 2014'', ''%W, %M %e,\n%Y'') |\n+---------------------------------------------------------+\n| 2014-06-02 |\n+---------------------------------------------------------+\n \nSELECT STR_TO_DATE(''Wednesday23423, June 2, 2014'', ''%W,\n%M %e, %Y'');\n+--------------------------------------------------------------+\n| STR_TO_DATE(''Wednesday23423, June 2, 2014'', ''%W, %M %e,\n%Y'') |\n+--------------------------------------------------------------+\n| NULL |\n+--------------------------------------------------------------+\n1 row in set, 1 warning (0.00 sec)\n \nSHOW WARNINGS;\n \n+---------+------+-----------------------------------------------------------------------------------+\n| Level | Code | Message |\n+---------+------+-----------------------------------------------------------------------------------+\n| Warning | 1411 | Incorrect datetime value:\n''Wednesday23423, June 2, 2014'' for function str_to_date |\n+---------+------+-----------------------------------------------------------------------------------+\n \nSELECT STR_TO_DATE(''Wednesday23423, June 2, 2014'', ''%W%#,\n%M %e, %Y'');\n+----------------------------------------------------------------+\n| STR_TO_DATE(''Wednesday23423, June 2, 2014'', ''%W%#, %M\n%e, %Y'') |\n+----------------------------------------------------------------+\n| 2014-06-02 |\n+----------------------------------------------------------------+ +description=This is the inverse of the DATE_FORMAT() function. It takes a string str and a\nformat string format. STR_TO_DATE() returns a DATETIME value if the format\nstring contains both date and time parts, or a DATE or TIME value if the\nstring contains only date or time parts.\n\nThe date, time, or datetime values contained in str should be given in the\nformat indicated by format. If str contains an illegal date, time, or datetime\nvalue, STR_TO_DATE() returns NULL. An illegal value also produces a warning.\n\nUnder specific SQL_MODE settings an error may also be generated if the str\nisn't a valid date:\n\n* ALLOW_INVALID_DATES\n* NO_ZERO_DATE\n* NO_ZERO_IN_DATE\n\nThe options that can be used by STR_TO_DATE(), as well as its inverse\nDATE_FORMAT() and the FROM_UNIXTIME() function, are:\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| %a | Short weekday name in current locale |\n| | (Variable lc_time_names). |\n+---------------------------+------------------------------------------------+\n| %b | Short form month name in current locale. For |\n| | locale en_US this is one of: |\n| | Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov |\n| | or Dec. |\n+---------------------------+------------------------------------------------+\n| %c | Month with 1 or 2 digits. |\n+---------------------------+------------------------------------------------+\n| %D | Day with English suffix 'th', 'nd', 'st' or |\n| | 'rd''. (1st, 2nd, 3rd...). |\n+---------------------------+------------------------------------------------+\n| %d | Day with 2 digits. |\n+---------------------------+------------------------------------------------+\n| %e | Day with 1 or 2 digits. |\n+---------------------------+------------------------------------------------+\n| %f | Microseconds 6 digits. |\n+---------------------------+------------------------------------------------+\n| %H | Hour with 2 digits between 00-23. |\n+---------------------------+------------------------------------------------+\n| %h | Hour with 2 digits between 01-12. |\n+---------------------------+------------------------------------------------+\n| %I | Hour with 2 digits between 01-12. |\n+---------------------------+------------------------------------------------+\n| %i | Minute with 2 digits. |\n+---------------------------+------------------------------------------------+\n| %j | Day of the year (001-366) |\n ... [ST_AREA] declaration=poly category=Polygon Properties -description=Returns as a double-precision number the area of the Polygon\nvalue poly, as measured in its spatial reference system.\n \nST_Area() and Area() are synonyms.\n \n\nSET @poly = ''Polygon((0 0,0 3,3 0,0 0),(1 1,1 2,2 1,1\n1))'';\n \nSELECT Area(GeomFromText(@poly));\n+---------------------------+\n| Area(GeomFromText(@poly)) |\n+---------------------------+\n| 4 |\n+---------------------------+ -[ST_ASBINARY] +description=Returns as a double-precision number the area of the Polygon value poly, as\nmeasured in its spatial reference system.\n\nST_Area() and Area() are synonyms.\n\nExamples\n--------\n\nSET @poly = 'Polygon((0 0,0 3,3 0,0 0),(1 1,1 2,2 1,1 1))';\n\nSELECT Area(GeomFromText(@poly));\n+---------------------------+\n| Area(GeomFromText(@poly)) |\n+---------------------------+\n| 4 |\n+---------------------------+\n\nURL: https://mariadb.com/kb/en/st_area/ +[ST_AsBinary] declaration=g category=WKB -description=Converts a value in internal geometry format to its WKB\nrepresentation and returns the binary result.\n \nST_AsBinary(), AsBinary(), ST_AsWKB() and AsWKB() are\nsynonyms,\n \n\nSET @poly = ST_GeomFromText(''POLYGON((0 0,0 1,1 1,1 0,0\n0))'');\nSELECT ST_AsBinary(@poly);\n \nSELECT ST_AsText(ST_GeomFromWKB(ST_AsWKB(@poly)));\n+--------------------------------------------+\n| ST_AsText(ST_GeomFromWKB(ST_AsWKB(@poly))) |\n+--------------------------------------------+\n| POLYGON((0 0,0 1,1 1,1 0,0 0)) |\n+--------------------------------------------+ -[ST_ASTEXT] +description=Converts a value in internal geometry format to its WKB representation and\nreturns the binary result.\n\nST_AsBinary(), AsBinary(), ST_AsWKB() and AsWKB() are synonyms,\n\nExamples\n--------\n\nSET @poly = ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))');\nSELECT ST_AsBinary(@poly);\n\nSELECT ST_AsText(ST_GeomFromWKB(ST_AsWKB(@poly)));\n+--------------------------------------------+\n| ST_AsText(ST_GeomFromWKB(ST_AsWKB(@poly))) |\n+--------------------------------------------+\n| POLYGON((0 0,0 1,1 1,1 0,0 0)) |\n+--------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_asbinary/ +[ST_AsGeoJSON] +declaration=g[, max_decimals[, options]] +category=GeoJSON +description=Returns the given geometry g as a GeoJSON element. The optional max_decimals\nlimits the maximum number of decimals displayed.\n\nThe optional options flag can be set to 1 to add a bounding box to the output.\n\nExamples\n--------\n\nSELECT ST_AsGeoJSON(ST_GeomFromText('POINT(5.3 7.2)'));\n+-------------------------------------------------+\n| ST_AsGeoJSON(ST_GeomFromText('POINT(5.3 7.2)')) |\n+-------------------------------------------------+\n| {"type": "Point", "coordinates": [5.3, 7.2]} |\n+-------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/geojson-st_asgeojson/ +[ST_AsText] declaration=g category=WKT -description=Converts a value in internal geometry format to its WKT\nrepresentation and returns the string result.\n \nST_AsText(), AsText(), ST_AsWKT() and AsWKT() are all\nsynonyms.\n \n\nSET @g = ''LineString(1 1,4 4,6 6)'';\n \nSELECT ST_AsText(ST_GeomFromText(@g));\n+--------------------------------+\n| ST_AsText(ST_GeomFromText(@g)) |\n+--------------------------------+\n| LINESTRING(1 1,4 4,6 6) |\n+--------------------------------+ +description=Converts a value in internal geometry format to its WKT representation and\nreturns the string result.\n\nST_AsText(), AsText(), ST_AsWKT() and AsWKT() are all synonyms.\n\nExamples\n--------\n\nSET @g = 'LineString(1 1,4 4,6 6)';\n\nSELECT ST_AsText(ST_GeomFromText(@g));\n+--------------------------------+\n| ST_AsText(ST_GeomFromText(@g)) |\n+--------------------------------+\n| LINESTRING(1 1,4 4,6 6) |\n+--------------------------------+\n\nURL: https://mariadb.com/kb/en/st_astext/ [ST_BOUNDARY] declaration=g category=Geometry Properties -description=Returns a geometry that is the closure of the combinatorial\nboundary of the geometry value g.\n \nBOUNDARY() is a synonym.\n \n\nSELECT ST_AsText(ST_Boundary(ST_GeomFromText(''LINESTRING(3\n3,0 0, -3 3)'')));\n+----------------------------------------------------------------------+\n| ST_AsText(ST_Boundary(ST_GeomFromText(''LINESTRING(3 3,0\n0, -3 3)''))) |\n+----------------------------------------------------------------------+\n| MULTIPOINT(3 3,-3 3) |\n+----------------------------------------------------------------------+\n \nSELECT ST_AsText(ST_Boundary(ST_GeomFromText(''POLYGON((3\n3,0 0, -3 3, 3 3))'')));\n+--------------------------------------------------------------------------+\n| ST_AsText(ST_Boundary(ST_GeomFromText(''POLYGON((3 3,0 0,\n-3 3, 3 3))''))) |\n+--------------------------------------------------------------------------+\n| LINESTRING(3 3,0 0,-3 3,3 3) |\n+--------------------------------------------------------------------------+ +description=Returns a geometry that is the closure of the combinatorial boundary of the\ngeometry value g.\n\nBOUNDARY() is a synonym.\n\nExamples\n--------\n\nSELECT ST_AsText(ST_Boundary(ST_GeomFromText('LINESTRING(3 3,0 0, -3 3)')));\n+----------------------------------------------------------------------+\n| ST_AsText(ST_Boundary(ST_GeomFromText('LINESTRING(3 3,0 0, -3 3)'))) |\n+----------------------------------------------------------------------+\n| MULTIPOINT(3 3,-3 3) |\n+----------------------------------------------------------------------+\n\nSELECT ST_AsText(ST_Boundary(ST_GeomFromText('POLYGON((3 3,0 0, -3 3, 3\n3))')));\n+--------------------------------------------------------------------------+\n| ST_AsText(ST_Boundary(ST_GeomFromText('POLYGON((3 3,0 0, -3 3, 3 3))'))) |\n+--------------------------------------------------------------------------+\n| LINESTRING(3 3,0 0,-3 3,3 3) |\n+--------------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_boundary/ [ST_BUFFER] declaration=g1,r category=Geometry Constructors -description=Returns a geometry that represents all points whose distance\nfrom geometry g1 is less than or equal to distance, or\nradius, r.\n \nUses for this function could include creating for example a\nnew geometry representing a buffer zone around an island.\n \nBUFFER() is a synonym.\n \n\nDetermining whether a point is within a buffer zone:\n \nSET @g1 = ST_GEOMFROMTEXT(''POLYGON((10 10, 10 20, 20 20, 20\n10, 10 10))'');\n \nSET @g2 = ST_GEOMFROMTEXT(''POINT(8 8)'');\n \nSELECT ST_WITHIN(@g2,ST_BUFFER(@g1,5));\n+---------------------------------+\n| ST_WITHIN(@g2,ST_BUFFER(@g1,5)) |\n+---------------------------------+\n| 1 |\n+---------------------------------+\n \nSELECT ST_WITHIN(@g2,ST_BUFFER(@g1,1));\n+---------------------------------+\n| ST_WITHIN(@g2,ST_BUFFER(@g1,1)) |\n+---------------------------------+\n| 0 |\n+---------------------------------+ +description=Returns a geometry that represents all points whose distance from geometry g1\nis less than or equal to distance, or radius, r.\n\nUses for this function could include creating for example a new geometry\nrepresenting a buffer zone around an island.\n\nBUFFER() is a synonym.\n\nExamples\n--------\n\nDetermining whether a point is within a buffer zone:\n\nSET @g1 = ST_GEOMFROMTEXT('POLYGON((10 10, 10 20, 20 20, 20 10, 10 10))');\n\nSET @g2 = ST_GEOMFROMTEXT('POINT(8 8)');\n\nSELECT ST_WITHIN(@g2,ST_BUFFER(@g1,5));\n+---------------------------------+\n| ST_WITHIN(@g2,ST_BUFFER(@g1,5)) |\n+---------------------------------+\n| 1 |\n+---------------------------------+\n\nSELECT ST_WITHIN(@g2,ST_BUFFER(@g1,1));\n+---------------------------------+\n| ST_WITHIN(@g2,ST_BUFFER(@g1,1)) |\n+---------------------------------+\n| 0 |\n+---------------------------------+\n\nURL: https://mariadb.com/kb/en/st_buffer/ [ST_CENTROID] declaration=mpoly category=Polygon Properties -description=Returns a point reflecting the mathematical centroid\n(geometric center) for the MultiPolygon mpoly. The resulting\npoint will not necessarily be on the MultiPolygon. \n \nST_Centroid() and Centroid() are synonyms.\n \n\nSET @poly = ST_GeomFromText(''POLYGON((0 0,20 0,20 20,0 20,0\n0))'');\nSELECT ST_AsText(ST_Centroid(@poly)) AS center;\n \n+--------------+\n| center |\n+--------------+\n| POINT(10 10) |\n+--------------+ +description=Returns a point reflecting the mathematical centroid (geometric center) for\nthe MultiPolygon mpoly. The resulting point will not necessarily be on the\nMultiPolygon.\n\nST_Centroid() and Centroid() are synonyms.\n\nExamples\n--------\n\nSET @poly = ST_GeomFromText('POLYGON((0 0,20 0,20 20,0 20,0 0))');\nSELECT ST_AsText(ST_Centroid(@poly)) AS center;\n+--------------+\n| center |\n+--------------+\n| POINT(10 10) |\n+--------------+\n\nURL: https://mariadb.com/kb/en/st_centroid/ [ST_CONTAINS] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether a geometry g1 completely\ncontains geometry g2.\n \nST_CONTAINS() uses object shapes, while CONTAINS(), based on\nthe original MySQL implementation, uses object bounding\nrectangles.\n \nST_CONTAINS tests the opposite relationship to ST_WITHIN().\n \n\nSET @g1 = ST_GEOMFROMTEXT(''POLYGON((175 150, 20 40, 50 60,\n125 100, 175 150))'');\n \nSET @g2 = ST_GEOMFROMTEXT(''POINT(174 149)'');\n \nSELECT ST_CONTAINS(@g1,@g2);\n+----------------------+\n| ST_CONTAINS(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n \nSET @g2 = ST_GEOMFROMTEXT(''POINT(175 151)'');\n \nSELECT ST_CONTAINS(@g1,@g2);\n+----------------------+\n| ST_CONTAINS(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+ +description=Returns 1 or 0 to indicate whether a geometry g1 completely contains geometry\ng2.\n\nST_CONTAINS() uses object shapes, while CONTAINS(), based on the original\nMySQL implementation, uses object bounding rectangles.\n\nST_CONTAINS tests the opposite relationship to ST_WITHIN().\n\nExamples\n--------\n\nSET @g1 = ST_GEOMFROMTEXT('POLYGON((175 150, 20 40, 50 60, 125 100, 175\n150))');\n\nSET @g2 = ST_GEOMFROMTEXT('POINT(174 149)');\n\nSELECT ST_CONTAINS(@g1,@g2);\n+----------------------+\n| ST_CONTAINS(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSET @g2 = ST_GEOMFROMTEXT('POINT(175 151)');\n\nSELECT ST_CONTAINS(@g1,@g2);\n+----------------------+\n| ST_CONTAINS(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/st-contains/ [ST_CONVEXHULL] -declaration=g +declaration= category=Geometry Constructors -description=Given a geometry, returns a geometry that is the minimum\nconvex geometry enclosing all geometries within the set.\nReturns NULL if the geometry value is NULL or an empty\nvalue.\n \nST_ConvexHull() and ConvexHull() are synonyms.\n \n\nThe ConvexHull of a single point is simply the single point:\n \nSET @g = ST_GEOMFROMTEXT(''Point(0 0)'');\n \nSELECT ST_ASTEXT(ST_CONVEXHULL(@g));\n+------------------------------+\n| ST_ASTEXT(ST_CONVEXHULL(@g)) |\n+------------------------------+\n| POINT(0 0) |\n+------------------------------+\n \nSET @g = ST_GEOMFROMTEXT(''MultiPoint(0 0, 1 2, 2 3)'');\n \nSELECT ST_ASTEXT(ST_CONVEXHULL(@g));\n+------------------------------+\n| ST_ASTEXT(ST_CONVEXHULL(@g)) |\n+------------------------------+\n| POLYGON((0 0,1 2,2 3,0 0)) |\n+------------------------------+\n \nSET @g = ST_GEOMFROMTEXT(''MultiPoint( 1 1, 2 2, 5 3, 7 2, 9\n3, 8 4, 6 6, 6 9, 4 9, 1 5 )'');\n \nSELECT ST_ASTEXT(ST_CONVEXHULL(@g));\n+----------------------------------------+\n| ST_ASTEXT(ST_CONVEXHULL(@g)) |\n+----------------------------------------+\n| POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1)) |\n+----------------------------------------+ +description=Given a geometry, returns a geometry that is the minimum convex geometry\nenclosing all geometries within the set. Returns NULL if the geometry value is\nNULL or an empty value.\n\nST_ConvexHull() and ConvexHull() are synonyms.\n\nExamples\n--------\n\nThe ConvexHull of a single point is simply the single point:\n\nSET @g = ST_GEOMFROMTEXT('Point(0 0)');\n\nSELECT ST_ASTEXT(ST_CONVEXHULL(@g));\n+------------------------------+\n| ST_ASTEXT(ST_CONVEXHULL(@g)) |\n+------------------------------+\n| POINT(0 0) |\n+------------------------------+\n\nSET @g = ST_GEOMFROMTEXT('MultiPoint(0 0, 1 2, 2 3)');\n\nSELECT ST_ASTEXT(ST_CONVEXHULL(@g));\n+------------------------------+\n| ST_ASTEXT(ST_CONVEXHULL(@g)) |\n+------------------------------+\n| POLYGON((0 0,1 2,2 3,0 0)) |\n+------------------------------+\n\nSET @g = ST_GEOMFROMTEXT('MultiPoint( 1 1, 2 2, 5 3, 7 2, 9 3, 8 4, 6 6, 6 9,\n4 9, 1 5 )');\n\nSELECT ST_ASTEXT(ST_CONVEXHULL(@g));\n+----------------------------------------+\n| ST_ASTEXT(ST_CONVEXHULL(@g)) |\n+----------------------------------------+\n| POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1)) |\n+----------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_convexhull/ [ST_CROSSES] declaration=g1,g2 category=Geometry Relations -description=Returns 1 if geometry g1 spatially crosses geometry g2.\nReturns NULL if g1 is a Polygon or a MultiPolygon, or if g2\nis a\nPoint or a MultiPoint. Otherwise, returns 0.\n \nThe term spatially crosses denotes a spatial relation\nbetween two\ngiven geometries that has the following properties:\nThe two geometries intersect\nTheir intersection results in a geometry that has a\ndimension that is one\n less than the maximum dimension of the two given geometries\nTheir intersection is not equal to either of the two given\ngeometries\n \nST_CROSSES() uses object shapes, while CROSSES(), based on\nthe original MySQL implementation, uses object bounding\nrectangles.\n \n\nSET @g1 = ST_GEOMFROMTEXT(''LINESTRING(174 149, 176 151)'');\n \nSET @g2 = ST_GEOMFROMTEXT(''POLYGON((175 150, 20 40, 50 60,\n125 100, 175 150))'');\n \nSELECT ST_CROSSES(@g1,@g2);\n+---------------------+\n| ST_CROSSES(@g1,@g2) |\n+---------------------+\n| 1 |\n+---------------------+\n \nSET @g1 = ST_GEOMFROMTEXT(''LINESTRING(176 149, 176 151)'');\n \nSELECT ST_CROSSES(@g1,@g2);\n+---------------------+\n| ST_CROSSES(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+ +description=Returns 1 if geometry g1 spatially crosses geometry g2. Returns NULL if g1 is\na Polygon or a MultiPolygon, or if g2 is a Point or a MultiPoint. Otherwise,\nreturns 0.\n\nThe term spatially crosses denotes a spatial relation between two given\ngeometries that has the following properties:\n\n* The two geometries intersect\n* Their intersection results in a geometry that has a dimension that is one\n less than the maximum dimension of the two given geometries\n* Their intersection is not equal to either of the two given geometries\n\nST_CROSSES() uses object shapes, while CROSSES(), based on the original MySQL\nimplementation, uses object bounding rectangles.\n\nExamples\n--------\n\nSET @g1 = ST_GEOMFROMTEXT('LINESTRING(174 149, 176 151)');\n\nSET @g2 = ST_GEOMFROMTEXT('POLYGON((175 150, 20 40, 50 60, 125 100, 175\n150))');\n\nSELECT ST_CROSSES(@g1,@g2);\n+---------------------+\n| ST_CROSSES(@g1,@g2) |\n+---------------------+\n| 1 |\n+---------------------+\n\nSET @g1 = ST_GEOMFROMTEXT('LINESTRING(176 149, 176 151)');\n\nSELECT ST_CROSSES(@g1,@g2);\n+---------------------+\n| ST_CROSSES(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/st-crosses/ [ST_DIFFERENCE] declaration=g1,g2 category=Geometry Relations -description=Returns a geometry representing the point set difference of\nthe given geometry values.\n \nExample\n \nSET @g1 = POINT(10,10), @g2 = POINT(20,20);\n \nSELECT ST_AsText(ST_Difference(@g1, @g2));\n+------------------------------------+\n| ST_AsText(ST_Difference(@g1, @g2)) |\n+------------------------------------+\n| POINT(10 10) |\n+------------------------------------+ +description=Returns a geometry representing the point set difference of the given geometry\nvalues.\n\nExample\n-------\n\nSET @g1 = POINT(10,10), @g2 = POINT(20,20);\n\nSELECT ST_AsText(ST_Difference(@g1, @g2));\n+------------------------------------+\n| ST_AsText(ST_Difference(@g1, @g2)) |\n+------------------------------------+\n| POINT(10 10) |\n+------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_difference/ [ST_DIMENSION] declaration=g category=Geometry Properties -description=Returns the inherent dimension of the geometry value g. The\nresult can\nbe\n \nDimension | Definition | \n \n -1 | empty geometry | \n \n 0 | geometry with no length or area | \n \n 1 | geometry with no area but nonzero length | \n \n 2 | geometry with nonzero area | \n \nST_Dimension() and Dimension() are synonyms.\n \n\nSELECT Dimension(GeomFromText(''LineString(1 1,2 2)''));\n+------------------------------------------------+\n| Dimension(GeomFromText(''LineString(1 1,2 2)'')) |\n+------------------------------------------------+\n| 1 |\n+------------------------------------------------+ +description=Returns the inherent dimension of the geometry value g. The result can be\n\n+------------------------------------+---------------------------------------+\n| Dimension | Definition |\n+------------------------------------+---------------------------------------+\n| -1 | empty geometry |\n+------------------------------------+---------------------------------------+\n| 0 | geometry with no length or area |\n+------------------------------------+---------------------------------------+\n| 1 | geometry with no area but nonzero |\n| | length |\n+------------------------------------+---------------------------------------+\n| 2 | geometry with nonzero area |\n+------------------------------------+---------------------------------------+\n\nST_Dimension() and Dimension() are synonyms.\n\nExamples\n--------\n\nSELECT Dimension(GeomFromText('LineString(1 1,2 2)'));\n+------------------------------------------------+\n| Dimension(GeomFromText('LineString(1 1,2 2)')) |\n+------------------------------------------------+\n| 1 |\n+------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_dimension/ [ST_DISJOINT] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether geometry g1 is spatially\ndisjoint from\n(does not intersect with) geometry g2.\n \nST_DISJOINT() uses object shapes, while DISJOINT(), based on\nthe original MySQL implementation, uses object bounding\nrectangles.\n \nST_DISJOINT() tests the opposite relationship to\nST_INTERSECTS().\n \n\nSET @g1 = ST_GEOMFROMTEXT(''POINT(0 0)'');\n \nSET @g2 = ST_GEOMFROMTEXT(''LINESTRING(2 0, 0 2)'');\n \nSELECT ST_DISJOINT(@g1,@g2);\n+----------------------+\n| ST_DISJOINT(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n \nSET @g2 = ST_GEOMFROMTEXT(''LINESTRING(0 0, 0 2)'');\n \nSELECT ST_DISJOINT(@g1,@g2);\n+----------------------+\n| ST_DISJOINT(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+ +description=Returns 1 or 0 to indicate whether geometry g1 is spatially disjoint from\n(does not intersect with) geometry g2.\n\nST_DISJOINT() uses object shapes, while DISJOINT(), based on the original\nMySQL implementation, uses object bounding rectangles.\n\nST_DISJOINT() tests the opposite relationship to ST_INTERSECTS().\n\nExamples\n--------\n\nSET @g1 = ST_GEOMFROMTEXT('POINT(0 0)');\n\nSET @g2 = ST_GEOMFROMTEXT('LINESTRING(2 0, 0 2)');\n\nSELECT ST_DISJOINT(@g1,@g2);\n+----------------------+\n| ST_DISJOINT(@g1,@g2) |\n+----------------------+\n| 1 |\n+----------------------+\n\nSET @g2 = ST_GEOMFROMTEXT('LINESTRING(0 0, 0 2)');\n\nSELECT ST_DISJOINT(@g1,@g2);\n+----------------------+\n| ST_DISJOINT(@g1,@g2) |\n+----------------------+\n| 0 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/st_disjoint/ [ST_DISTANCE] declaration=g1,g2 category=Geometry Relations -description=Returns the distance between two geometries, or null if not\ngiven valid inputs.\n \nExample\n \nSELECT ST_Distance(POINT(1,2),POINT(2,2));\n+------------------------------------+\n| ST_Distance(POINT(1,2),POINT(2,2)) |\n+------------------------------------+\n| 1 |\n+------------------------------------+ +description=Returns the distance between two geometries, or null if not given valid inputs.\n\nExample\n-------\n\nSELECT ST_Distance(POINT(1,2),POINT(2,2));\n+------------------------------------+\n| ST_Distance(POINT(1,2),POINT(2,2)) |\n+------------------------------------+\n| 1 |\n+------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_distance/ +[ST_DISTANCE_SPHERE] +declaration=g1,g2,[r] +category=Geometry Relations +description=Returns the spherical distance between two geometries (point or multipoint) on\na sphere with the optional radius r (default is the Earth radius if r is not\nspecified), or NULL if not given valid inputs.\n\nExample\n-------\n\nset @zenica = ST_GeomFromText('POINT(17.907743 44.203438)');\nset @sarajevo = ST_GeomFromText('POINT(18.413076 43.856258)');\nSELECT ST_Distance_Sphere(@zenica, @sarajevo);\n55878.59337591705\n\nURL: https://mariadb.com/kb/en/st_distance_sphere/ [ST_ENDPOINT] declaration=ls category=LineString Properties -description=Returns the Point that is the endpoint of the\nLineString value ls.\n \nST_EndPoint() and EndPoint() are synonyms.\n \n\nSET @ls = ''LineString(1 1,2 2,3 3)'';\n \nSELECT AsText(EndPoint(GeomFromText(@ls)));\n+-------------------------------------+\n| AsText(EndPoint(GeomFromText(@ls))) |\n+-------------------------------------+\n| POINT(3 3) |\n+-------------------------------------+ +description=Returns the Point that is the endpoint of the LineString value ls.\n\nST_EndPoint() and EndPoint() are synonyms.\n\nExamples\n--------\n\nSET @ls = 'LineString(1 1,2 2,3 3)';\n\nSELECT AsText(EndPoint(GeomFromText(@ls)));\n+-------------------------------------+\n| AsText(EndPoint(GeomFromText(@ls))) |\n+-------------------------------------+\n| POINT(3 3) |\n+-------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_endpoint/ [ST_ENVELOPE] declaration=g category=Geometry Properties -description=Returns the Minimum Bounding Rectangle (MBR) for the\ngeometry value g. The result is returned as a Polygon value.\n \nThe polygon is defined by the corner points of the bounding\nbox:\n \nPOLYGON((MINX MINY, MAXX MINY, MAXX MAXY, MINX MAXY, MINX\nMINY))\n \nST_ENVELOPE() and ENVELOPE() are synonyms.\n \n\nSELECT AsText(ST_ENVELOPE(GeomFromText(''LineString(1 1,4\n4)'')));\n+----------------------------------------------------------+\n| AsText(ST_ENVELOPE(GeomFromText(''LineString(1 1,4 4)'')))\n|\n+----------------------------------------------------------+\n| POLYGON((1 1,4 1,4 4,1 4,1 1)) |\n+----------------------------------------------------------+ +description=Returns the Minimum Bounding Rectangle (MBR) for the geometry value g. The\nresult is returned as a Polygon value.\n\nThe polygon is defined by the corner points of the bounding box:\n\nPOLYGON((MINX MINY, MAXX MINY, MAXX MAXY, MINX MAXY, MINX MINY))\n\nST_ENVELOPE() and ENVELOPE() are synonyms.\n\nExamples\n--------\n\nSELECT AsText(ST_ENVELOPE(GeomFromText('LineString(1 1,4 4)')));\n+----------------------------------------------------------+\n| AsText(ST_ENVELOPE(GeomFromText('LineString(1 1,4 4)'))) |\n+----------------------------------------------------------+\n| POLYGON((1 1,4 1,4 4,1 4,1 1)) |\n+----------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_envelope/ [ST_EQUALS] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether geometry g1 is spatially\nequal to geometry g2.\n \nST_EQUALS() uses object shapes, while EQUALS(), based on the\noriginal MySQL implementation, uses object bounding\nrectangles.\n \n\nSET @g1 = ST_GEOMFROMTEXT(''LINESTRING(174 149, 176 151)'');\n \nSET @g2 = ST_GEOMFROMTEXT(''LINESTRING(176 151, 174 149)'');\n \nSELECT ST_EQUALS(@g1,@g2);\n+--------------------+\n| ST_EQUALS(@g1,@g2) |\n+--------------------+\n| 1 |\n+--------------------+\n \nSET @g1 = ST_GEOMFROMTEXT(''POINT(0 2)'');\n \nSET @g1 = ST_GEOMFROMTEXT(''POINT(2 0)'');\n \nSELECT ST_EQUALS(@g1,@g2);\n+--------------------+\n| ST_EQUALS(@g1,@g2) |\n+--------------------+\n| 0 |\n+--------------------+ -[ST_EXTERIORRING] +description=Returns 1 or 0 to indicate whether geometry g1 is spatially equal to geometry\ng2.\n\nST_EQUALS() uses object shapes, while EQUALS(), based on the original MySQL\nimplementation, uses object bounding rectangles.\n\nExamples\n--------\n\nSET @g1 = ST_GEOMFROMTEXT('LINESTRING(174 149, 176 151)');\n\nSET @g2 = ST_GEOMFROMTEXT('LINESTRING(176 151, 174 149)');\n\nSELECT ST_EQUALS(@g1,@g2);\n+--------------------+\n| ST_EQUALS(@g1,@g2) |\n+--------------------+\n| 1 |\n+--------------------+\n\nSET @g1 = ST_GEOMFROMTEXT('POINT(0 2)');\n\nSET @g1 = ST_GEOMFROMTEXT('POINT(2 0)');\n\nSELECT ST_EQUALS(@g1,@g2);\n+--------------------+\n| ST_EQUALS(@g1,@g2) |\n+--------------------+\n| 0 |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/st-equals/ +[ST_ExteriorRing] declaration=poly category=Polygon Properties -description=Returns the exterior ring of the Polygon value poly as a\nLineString.\n \nST_ExteriorRing() and ExteriorRing() are synonyms.\n \n\nSET @poly = ''Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2\n1,1 1))'';\n \nSELECT AsText(ExteriorRing(GeomFromText(@poly)));\n+-------------------------------------------+\n| AsText(ExteriorRing(GeomFromText(@poly))) |\n+-------------------------------------------+\n| LINESTRING(0 0,0 3,3 3,3 0,0 0) |\n+-------------------------------------------+ -[ST_GEOMCOLLFROMTEXT] -declaration=wkt[,srid] -category=WKT -description=Constructs a GEOMETRYCOLLECTION value using its WKT \nrepresentation and SRID.\n \nST_GeomCollFromText(), ST_GeometryCollectionFromText(),\nGeomCollFromText() and GeometryCollectionFromText() are all\nsynonyms.\n \nExample\n \nCREATE TABLE gis_geometrycollection (g GEOMETRYCOLLECTION);\nSHOW FIELDS FROM gis_geometrycollection;\n \nINSERT INTO gis_geometrycollection VALUES\n (GeomCollFromText(''GEOMETRYCOLLECTION(POINT(0 0),\nLINESTRING(0 0,10 10))'')),\n (GeometryFromWKB(AsWKB(GeometryCollection(Point(44, 6),\nLineString(Point(3, 6), Point(7, 9)))))),\n (GeomFromText(''GeometryCollection()'')),\n (GeomFromText(''GeometryCollection EMPTY'')); -[ST_GEOMCOLLFROMWKB] -declaration=wkb[,srid] -category=WKB -description=Constructs a GEOMETRYCOLLECTION value using its WKB\nrepresentation and SRID.\n \nST_GeomCollFromWKB(), ST_GeometryCollectionFromWKB(),\nGeomCollFromWKB() and GeometryCollectionFromWKB() are\nsynonyms.\n \n\nSET @g =\nST_AsBinary(ST_GeomFromText(''GEOMETRYCOLLECTION(POLYGON((5\n5,10 5,10 10,5 5)),POINT(10 10))''));\n \nSELECT ST_AsText(ST_GeomCollFromWKB(@g));\n+----------------------------------------------------------------+\n| ST_AsText(ST_GeomCollFromWKB(@g)) |\n+----------------------------------------------------------------+\n| GEOMETRYCOLLECTION(POLYGON((5 5,10 5,10 10,5 5)),POINT(10\n10)) |\n+----------------------------------------------------------------+ +description=Returns the exterior ring of the Polygon value poly as a LineString.\n\nST_ExteriorRing() and ExteriorRing() are synonyms.\n\nExamples\n--------\n\nSET @poly = 'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))';\n\nSELECT AsText(ExteriorRing(GeomFromText(@poly)));\n+-------------------------------------------+\n| AsText(ExteriorRing(GeomFromText(@poly))) |\n+-------------------------------------------+\n| LINESTRING(0 0,0 3,3 3,3 0,0 0) |\n+-------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_exteriorring/ [ST_GEOMETRYN] declaration=gc,N category=Geometry Properties -description=Returns the N-th geometry in the GeometryCollection gc.\nGeometries are numbered beginning with 1.\n \nST_GeometryN() and GeometryN() are synonyms.\n \nExample\n \nSET @gc = ''GeometryCollection(Point(1 1),LineString(12 14,\n9 11))'';\n \nSELECT AsText(GeometryN(GeomFromText(@gc),1));\n+----------------------------------------+\n| AsText(GeometryN(GeomFromText(@gc),1)) |\n+----------------------------------------+\n| POINT(1 1) |\n+----------------------------------------+ +description=Returns the N-th geometry in the GeometryCollection gc. Geometries are\nnumbered beginning with 1.\n\nST_GeometryN() and GeometryN() are synonyms.\n\nExample\n-------\n\nSET @gc = 'GeometryCollection(Point(1 1),LineString(12 14, 9 11))';\n\nSELECT AsText(GeometryN(GeomFromText(@gc),1));\n+----------------------------------------+\n| AsText(GeometryN(GeomFromText(@gc),1)) |\n+----------------------------------------+\n| POINT(1 1) |\n+----------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_geometryn/ [ST_GEOMETRYTYPE] declaration=g category=Geometry Properties -description=Returns as a string the name of the geometry type of which\nthe\ngeometry instance g is a member. The name corresponds to one\nof the\ninstantiable Geometry subclasses.\n \nST_GeometryType() and GeometryType() are synonyms.\n \n\nSELECT GeometryType(GeomFromText(''POINT(1 1)''));\n+------------------------------------------+\n| GeometryType(GeomFromText(''POINT(1 1)'')) |\n+------------------------------------------+\n| POINT |\n+------------------------------------------+ -[ST_GEOMFROMTEXT] +description=Returns as a string the name of the geometry type of which the geometry\ninstance g is a member. The name corresponds to one of the instantiable\nGeometry subclasses.\n\nST_GeometryType() and GeometryType() are synonyms.\n\nExamples\n--------\n\nSELECT GeometryType(GeomFromText('POINT(1 1)'));\n+------------------------------------------+\n| GeometryType(GeomFromText('POINT(1 1)')) |\n+------------------------------------------+\n| POINT |\n+------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_geometrytype/ +[ST_GeomCollFromText] declaration=wkt[,srid] category=WKT -description=Constructs a geometry value of any type using its WKT\nrepresentation and SRID.\n \nGeomFromText(), GeometryFromText(), ST_GeomFromText() and\nST_GeometryFromText() are all synonyms.\n \nExample\n \nSET @g = ST_GEOMFROMTEXT(''POLYGON((1 1,1 5,4 9,6 9,9 3,7\n2,1 1))''); -[ST_GEOMFROMWKB] +description=Constructs a GEOMETRYCOLLECTION value using its WKT representation and SRID.\n\nST_GeomCollFromText(), ST_GeometryCollectionFromText(), GeomCollFromText() and\nGeometryCollectionFromText() are all synonyms.\n\nExample\n-------\n\nCREATE TABLE gis_geometrycollection (g GEOMETRYCOLLECTION);\nSHOW FIELDS FROM gis_geometrycollection;\nINSERT INTO gis_geometrycollection VALUES\n (GeomCollFromText('GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0,10\n10))')),\n (GeometryFromWKB(AsWKB(GeometryCollection(Point(44, 6),\nLineString(Point(3, 6), Point(7, 9)))))),\n (GeomFromText('GeometryCollection()')),\n (GeomFromText('GeometryCollection EMPTY'));\n\nURL: https://mariadb.com/kb/en/st_geomcollfromtext/ +[ST_GeomCollFromWKB] declaration=wkb[,srid] category=WKB -description=Constructs a geometry value of any type using its WKB\nrepresentation and SRID.\n \nST_GeomFromWKB(), ST_GeometryFromWKB(), GeomFromWKB() and\nGeometryFromWKB() are synonyms.\n \n\nSET @g = ST_AsBinary(ST_LineFromText(''LINESTRING(0 4, 4\n6)''));\n \nSELECT ST_AsText(ST_GeomFromWKB(@g));\n+-------------------------------+\n| ST_AsText(ST_GeomFromWKB(@g)) |\n+-------------------------------+\n| LINESTRING(0 4,4 6) |\n+-------------------------------+ -[ST_INTERIORRINGN] -declaration=poly,N -category=Polygon Properties -description=Returns the N-th interior ring for the Polygon value poly as\na LineString. Rings are numbered beginning with 1.\n \nST_InteriorRingN() and InteriorRingN() are synonyms.\n \n\nSET @poly = ''Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2\n1,1 1))'';\n \nSELECT AsText(InteriorRingN(GeomFromText(@poly),1));\n+----------------------------------------------+\n| AsText(InteriorRingN(GeomFromText(@poly),1)) |\n+----------------------------------------------+\n| LINESTRING(1 1,1 2,2 2,2 1,1 1) |\n+----------------------------------------------+ +description=Constructs a GEOMETRYCOLLECTION value using its WKB representation and SRID.\n\nST_GeomCollFromWKB(), ST_GeometryCollectionFromWKB(), GeomCollFromWKB() and\nGeometryCollectionFromWKB() are synonyms.\n\nExamples\n--------\n\nSET @g = ST_AsBinary(ST_GeomFromText('GEOMETRYCOLLECTION(\n POLYGON((5 5,10 5,10 10,5 5)),POINT(10 10))'));\n\nSELECT ST_AsText(ST_GeomCollFromWKB(@g));\n+----------------------------------------------------------------+\n| ST_AsText(ST_GeomCollFromWKB(@g)) |\n+----------------------------------------------------------------+\n| GEOMETRYCOLLECTION(POLYGON((5 5,10 5,10 10,5 5)),POINT(10 10)) |\n+----------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_geomcollfromwkb/ +[ST_GeomFromGeoJSON] +declaration=g[, option] +category=GeoJSON +description=Given a GeoJSON input g, returns a geometry object. The option specifies what\nto do if g contains geometries with coordinate dimensions higher than 2.\n\n+---------------------------+------------------------------------------------+\n| Option | Description |\n+---------------------------+------------------------------------------------+\n| 1 | Return an error (the default) |\n+---------------------------+------------------------------------------------+\n| 2 - 4 | The document is accepted, but the coordinates |\n| | for higher coordinate dimensions are stripped |\n| | off. |\n+---------------------------+------------------------------------------------+\n\nNote that this function did not work correctly before MariaDB 10.2.8 - see\nMDEV-12180.\n\nExamples\n--------\n\nSET @j = '{ "type": "Point", "coordinates": [5.3, 15.0]}';\n\nSELECT ST_AsText(ST_GeomFromGeoJSON(@j));\n+-----------------------------------+\n| ST_AsText(ST_GeomFromGeoJSON(@j)) |\n+-----------------------------------+\n| POINT(5.3 15) |\n+-----------------------------------+\n\nURL: https://mariadb.com/kb/en/st_geomfromgeojson/ +[ST_GeomFromText] +declaration=wkt[,srid] +category=WKT +description=Constructs a geometry value of any type using its WKT representation and SRID.\n\nGeomFromText(), GeometryFromText(), ST_GeomFromText() and\nST_GeometryFromText() are all synonyms.\n\nExample\n-------\n\nSET @g = ST_GEOMFROMTEXT('POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1))');\n\nURL: https://mariadb.com/kb/en/st_geomfromtext/ +[ST_GeomFromWKB] +declaration=wkb[,srid] +category=WKB +description=Constructs a geometry value of any type using its WKB representation and SRID.\n\nST_GeomFromWKB(), ST_GeometryFromWKB(), GeomFromWKB() and GeometryFromWKB()\nare synonyms.\n\nExamples\n--------\n\nSET @g = ST_AsBinary(ST_LineFromText('LINESTRING(0 4, 4 6)'));\n\nSELECT ST_AsText(ST_GeomFromWKB(@g));\n+-------------------------------+\n| ST_AsText(ST_GeomFromWKB(@g)) |\n+-------------------------------+\n| LINESTRING(0 4,4 6) |\n+-------------------------------+\n\nURL: https://mariadb.com/kb/en/st_geomfromwkb/ [ST_INTERSECTION] declaration=g1,g2 category=Geometry Constructors -description=Returns a geometry that is the intersection, or shared\nportion, of geometry g1 and geometry g2.\n \n\nSET @g1 = ST_GEOMFROMTEXT(''POINT(2 1)'');\n \nSET @g2 = ST_GEOMFROMTEXT(''LINESTRING(2 1, 0 2)'');\n \nSELECT ASTEXT(ST_INTERSECTION(@g1,@g2));\n+----------------------------------+\n| ASTEXT(ST_INTERSECTION(@g1,@g2)) |\n+----------------------------------+\n| POINT(2 1) |\n+----------------------------------+ +description=Returns a geometry that is the intersection, or shared portion, of geometry g1\nand geometry g2.\n\nExamples\n--------\n\nSET @g1 = ST_GEOMFROMTEXT('POINT(2 1)');\n\nSET @g2 = ST_GEOMFROMTEXT('LINESTRING(2 1, 0 2)');\n\nSELECT ASTEXT(ST_INTERSECTION(@g1,@g2));\n+----------------------------------+\n| ASTEXT(ST_INTERSECTION(@g1,@g2)) |\n+----------------------------------+\n| POINT(2 1) |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/st_intersection/ [ST_INTERSECTS] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether geometry g1 spatially\nintersects geometry g2.\n \nST_INTERSECTS() uses object shapes, while INTERSECTS(),\nbased on the original MySQL implementation, uses object\nbounding rectangles.\n \nST_INTERSECTS() tests the opposite relationship to\nST_DISJOINT().\n \n\nSET @g1 = ST_GEOMFROMTEXT(''POINT(0 0)'');\n \nSET @g2 = ST_GEOMFROMTEXT(''LINESTRING(0 0, 0 2)'');\n \nSELECT ST_INTERSECTS(@g1,@g2);\n+------------------------+\n| ST_INTERSECTS(@g1,@g2) |\n+------------------------+\n| 1 |\n+------------------------+\n \nSET @g2 = ST_GEOMFROMTEXT(''LINESTRING(2 0, 0 2)'');\n \nSELECT ST_INTERSECTS(@g1,@g2);\n+------------------------+\n| ST_INTERSECTS(@g1,@g2) |\n+------------------------+\n| 0 |\n+------------------------+ +description=Returns 1 or 0 to indicate whether geometry g1 spatially intersects geometry\ng2.\n\nST_INTERSECTS() uses object shapes, while INTERSECTS(), based on the original\nMySQL implementation, uses object bounding rectangles.\n\nST_INTERSECTS() tests the opposite relationship to ST_DISJOINT().\n\nExamples\n--------\n\nSET @g1 = ST_GEOMFROMTEXT('POINT(0 0)');\n\nSET @g2 = ST_GEOMFROMTEXT('LINESTRING(0 0, 0 2)');\n\nSELECT ST_INTERSECTS(@g1,@g2);\n+------------------------+\n| ST_INTERSECTS(@g1,@g2) |\n+------------------------+\n| 1 |\n+------------------------+\n\nSET @g2 = ST_GEOMFROMTEXT('LINESTRING(2 0, 0 2)');\n\nSELECT ST_INTERSECTS(@g1,@g2);\n+------------------------+\n| ST_INTERSECTS(@g1,@g2) |\n+------------------------+\n| 0 |\n+------------------------+\n\nURL: https://mariadb.com/kb/en/st-intersects/ [ST_ISCLOSED] declaration=g category=Geometry Properties -description=Returns 1 if a given LINESTRING''s start and end points are\nthe same, or 0 if they are not the same. Before MariaDB\n10.1.5, returns NULL if not given a LINESTRING. After\nMariaDB 10.1.5, returns -1.\n \nST_IsClosed() and IsClosed() are synonyms.\n \n\nSET @ls = ''LineString(0 0, 0 4, 4 4, 0 0)'';\n \nSELECT ST_ISCLOSED(GEOMFROMTEXT(@ls));\n+--------------------------------+\n| ST_ISCLOSED(GEOMFROMTEXT(@ls)) |\n+--------------------------------+\n| 1 |\n+--------------------------------+\n \nSET @ls = ''LineString(0 0, 0 4, 4 4, 0 1)'';\n \nSELECT ST_ISCLOSED(GEOMFROMTEXT(@ls));\n+--------------------------------+\n| ST_ISCLOSED(GEOMFROMTEXT(@ls)) |\n+--------------------------------+\n| 0 |\n+--------------------------------+ +description=Returns 1 if a given LINESTRING's start and end points are the same, or 0 if\nthey are not the same. Before MariaDB 10.1.5, returns NULL if not given a\nLINESTRING. After MariaDB 10.1.5, returns -1.\n\nST_IsClosed() and IsClosed() are synonyms.\n\nExamples\n--------\n\nSET @ls = 'LineString(0 0, 0 4, 4 4, 0 0)';\nSELECT ST_ISCLOSED(GEOMFROMTEXT(@ls));\n+--------------------------------+\n| ST_ISCLOSED(GEOMFROMTEXT(@ls)) |\n+--------------------------------+\n| 1 |\n+--------------------------------+\n\nSET @ls = 'LineString(0 0, 0 4, 4 4, 0 1)';\nSELECT ST_ISCLOSED(GEOMFROMTEXT(@ls));\n+--------------------------------+\n| ST_ISCLOSED(GEOMFROMTEXT(@ls)) |\n+--------------------------------+\n| 0 |\n+--------------------------------+\n\nURL: https://mariadb.com/kb/en/st_isclosed/ [ST_ISEMPTY] declaration=g category=Geometry Properties -description=IsEmpty is a function defined by the OpenGIS specification,\nbut is not fully implemented by MariaDB or MySQL. \n \nSince MariaDB and MySQL do not support GIS EMPTY values such\nas POINT EMPTY, as implemented it simply returns 1 if the\ngeometry value g is invalid, 0 if it is valid, and NULL if\nthe argument is NULL.\n \nST_IsEmpty() and IsEmpty() are synonyms. -[ST_ISRING] +description=IsEmpty is a function defined by the OpenGIS specification, but is not fully\nimplemented by MariaDB or MySQL.\n\nSince MariaDB and MySQL do not support GIS EMPTY values such as POINT EMPTY,\nas implemented it simply returns 1 if the geometry value g is invalid, 0 if it\nis valid, and NULL if the argument is NULL.\n\nST_IsEmpty() and IsEmpty() are synonyms.\n\nURL: https://mariadb.com/kb/en/st_isempty/ +[ST_InteriorRingN] +declaration=poly,N +category=Polygon Properties +description=Returns the N-th interior ring for the Polygon value poly as a LineString.\nRings are numbered beginning with 1.\n\nST_InteriorRingN() and InteriorRingN() are synonyms.\n\nExamples\n--------\n\nSET @poly = 'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))';\n\nSELECT AsText(InteriorRingN(GeomFromText(@poly),1));\n+----------------------------------------------+\n| AsText(InteriorRingN(GeomFromText(@poly),1)) |\n+----------------------------------------------+\n| LINESTRING(1 1,1 2,2 2,2 1,1 1) |\n+----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_interiorringn/ +[ST_IsRing] declaration=g category=Geometry Properties -description=Returns true if a given LINESTRING is a ring, that is, both\nST_IsClosed and ST_IsSimple. A simple curve does not pass\nthrough the same point more than once. However, see\nMDEV-7510.\n \nSt_IsRing() and IsRing() are synonyms. -[ST_ISSIMPLE] +description=Returns true if a given LINESTRING is a ring, that is, both ST_IsClosed and\nST_IsSimple. A simple curve does not pass through the same point more than\nonce. However, see MDEV-7510.\n\nSt_IsRing() and IsRing() are synonyms.\n\nURL: https://mariadb.com/kb/en/st_isring/ +[ST_IsSimple] declaration=g category=Geometry Properties -description=Returns true if the given Geometry has no anomalous\ngeometric points, false if it does, or NULL if given a NULL\nvalue.\n \nST_IsSimple() and IsSimple() are synonyms.\n \n\nA POINT is always simple.\n \nSET @g = ''Point(1 2)'';\n \nSELECT ST_ISSIMPLE(GEOMFROMTEXT(@g));\n+-------------------------------+\n| ST_ISSIMPLE(GEOMFROMTEXT(@g)) |\n+-------------------------------+\n| 1 |\n+-------------------------------+ +description=Returns true if the given Geometry has no anomalous geometric points, false if\nit does, or NULL if given a NULL value.\n\nST_IsSimple() and IsSimple() are synonyms.\n\nExamples\n--------\n\nA POINT is always simple.\n\nSET @g = 'Point(1 2)';\n\nSELECT ST_ISSIMPLE(GEOMFROMTEXT(@g));\n+-------------------------------+\n| ST_ISSIMPLE(GEOMFROMTEXT(@g)) |\n+-------------------------------+\n| 1 |\n+-------------------------------+\n\nURL: https://mariadb.com/kb/en/st_issimple/ [ST_LENGTH] declaration=ls category=Geometry Relations -description=Returns as a double-precision number the length of the\nLineString value ls in its associated spatial reference.\n \n\nSET @ls = ''LineString(1 1,2 2,3 3)'';\n \nSELECT ST_LENGTH(ST_GeomFromText(@ls));\n+---------------------------------+\n| ST_LENGTH(ST_GeomFromText(@ls)) |\n+---------------------------------+\n| 2.82842712474619 |\n+---------------------------------+ -[ST_LINEFROMTEXT] +description=Returns as a double-precision number the length of the LineString value ls in\nits associated spatial reference.\n\nExamples\n--------\n\nSET @ls = 'LineString(1 1,2 2,3 3)';\n\nSELECT ST_LENGTH(ST_GeomFromText(@ls));\n+---------------------------------+\n| ST_LENGTH(ST_GeomFromText(@ls)) |\n+---------------------------------+\n| 2.82842712474619 |\n+---------------------------------+\n\nURL: https://mariadb.com/kb/en/st_length/ +[ST_LineFromText] declaration=wkt[,srid] category=WKT -description=Constructs a LINESTRING value using its WKT representation\nand SRID.\n \nST_LineFromText(), ST_LineStringFromText(),\nST_LineFromText() and ST_LineStringFromText() are all\nsynonyms.\n \n\nCREATE TABLE gis_line (g LINESTRING);\nSHOW FIELDS FROM gis_line;\n \nINSERT INTO gis_line VALUES\n (LineFromText(''LINESTRING(0 0,0 10,10 0)'')),\n (LineStringFromText(''LINESTRING(10 10,20 10,20 20,10 20,10\n10)'')),\n (LineStringFromWKB(AsWKB(LineString(Point(10, 10),\nPoint(40, 10))))); -[ST_LINEFROMWKB] +description=Constructs a LINESTRING value using its WKT representation and SRID.\n\nST_LineFromText(), ST_LineStringFromText(), ST_LineFromText() and\nST_LineStringFromText() are all synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_line (g LINESTRING);\nSHOW FIELDS FROM gis_line;\nINSERT INTO gis_line VALUES\n (LineFromText('LINESTRING(0 0,0 10,10 0)')),\n (LineStringFromText('LINESTRING(10 10,20 10,20 20,10 20,10 10)')),\n (LineStringFromWKB(AsWKB(LineString(Point(10, 10), Point(40, 10)))));\n\nURL: https://mariadb.com/kb/en/st_linefromtext/ +[ST_LineFromWKB] declaration=wkb[,srid] category=WKB -description=Constructs a LINESTRING value using its WKB representation\nand SRID.\n \nST_LineFromWKB(), LineFromWKB(), ST_LineStringFromWKB(), and\nLineStringFromWKB() are synonyms.\n \n\nSET @g = ST_AsBinary(ST_LineFromText(''LineString(0 4,4\n6)''));\n \nSELECT ST_AsText(ST_LineFromWKB(@g)) AS l;\n \n+---------------------+\n| l |\n+---------------------+\n| LINESTRING(0 4,4 6) |\n+---------------------+ +description=Constructs a LINESTRING value using its WKB representation and SRID.\n\nST_LineFromWKB(), LineFromWKB(), ST_LineStringFromWKB(), and\nLineStringFromWKB() are synonyms.\n\nExamples\n--------\n\nSET @g = ST_AsBinary(ST_LineFromText('LineString(0 4,4 6)'));\n\nSELECT ST_AsText(ST_LineFromWKB(@g)) AS l;\n+---------------------+\n| l |\n+---------------------+\n| LINESTRING(0 4,4 6) |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/st_linefromwkb/ [ST_NUMGEOMETRIES] declaration=gc category=Geometry Properties -description=Returns the number of geometries in the GeometryCollection\ngc.\n \nST_NumGeometries() and NumGeometries() are synonyms.\n \nExample\n \nSET @gc = ''GeometryCollection(Point(1 1),LineString(2 2, 3\n3))'';\n \nSELECT NUMGEOMETRIES(GeomFromText(@gc));\n+----------------------------------+\n| NUMGEOMETRIES(GeomFromText(@gc)) |\n+----------------------------------+\n| 2 |\n+----------------------------------+ -[ST_NUMINTERIORRINGS] -declaration=poly -category=Polygon Properties -description=Returns an integer containing the number of interior rings\nin the Polygon value poly.\n \nNote that according the the OpenGIS standard, a POLYGON\nshould have exactly one ExteriorRing and all other rings\nshould lie within that ExteriorRing and thus be the\nInteriorRings. Practically, however, some systems, including\nMariaDB''s, permit polygons to have several\n''ExteriorRings''. In the case of there being multiple,\nnon-overlapping exterior rings ST_NumInteriorRings() will\nreturn 1.\n \nST_NumInteriorRings() and NumInteriorRings() are synonyms.\n \n\nSET @poly = ''Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2\n1,1 1))'';\n \nSELECT NumInteriorRings(GeomFromText(@poly));\n+---------------------------------------+\n| NumInteriorRings(GeomFromText(@poly)) |\n+---------------------------------------+\n| 1 |\n+---------------------------------------+\n \nNon-overlapping ''polygon'':\n \nSELECT ST_NumInteriorRings(ST_PolyFromText(''POLYGON((0 0,10\n0,10 10,0 10,0 0),\n (-1 -1,-5 -1,-5 -5,-1 -5,-1 -1))'')) AS NumInteriorRings;\n \n+------------------+\n| NumInteriorRings |\n+------------------+\n| 1 |\n+------------------+ +description=Returns the number of geometries in the GeometryCollection gc.\n\nST_NumGeometries() and NumGeometries() are synonyms.\n\nExample\n-------\n\nSET @gc = 'GeometryCollection(Point(1 1),LineString(2 2, 3 3))';\n\nSELECT NUMGEOMETRIES(GeomFromText(@gc));\n+----------------------------------+\n| NUMGEOMETRIES(GeomFromText(@gc)) |\n+----------------------------------+\n| 2 |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/st_numgeometries/ [ST_NUMPOINTS] declaration=ls category=LineString Properties -description=Returns the number of Point objects in the LineString\nvalue ls.\n \nST_NumPoints() and NumPoints() are synonyms.\n \n\nSET @ls = ''LineString(1 1,2 2,3 3)'';\n \nSELECT NumPoints(GeomFromText(@ls));\n+------------------------------+\n| NumPoints(GeomFromText(@ls)) |\n+------------------------------+\n| 3 |\n+------------------------------+ +description=Returns the number of Point objects in the LineString value ls.\n\nST_NumPoints() and NumPoints() are synonyms.\n\nExamples\n--------\n\nSET @ls = 'LineString(1 1,2 2,3 3)';\n\nSELECT NumPoints(GeomFromText(@ls));\n+------------------------------+\n| NumPoints(GeomFromText(@ls)) |\n+------------------------------+\n| 3 |\n+------------------------------+\n\nURL: https://mariadb.com/kb/en/st_numpoints/ +[ST_NumInteriorRings] +declaration=poly +category=Polygon Properties +description=Returns an integer containing the number of interior rings in the Polygon\nvalue poly.\n\nNote that according the the OpenGIS standard, a POLYGON should have exactly\none ExteriorRing and all other rings should lie within that ExteriorRing and\nthus be the InteriorRings. Practically, however, some systems, including\nMariaDB's, permit polygons to have several 'ExteriorRings'. In the case of\nthere being multiple, non-overlapping exterior rings ST_NumInteriorRings()\nwill return 1.\n\nST_NumInteriorRings() and NumInteriorRings() are synonyms.\n\nExamples\n--------\n\nSET @poly = 'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))';\n\nSELECT NumInteriorRings(GeomFromText(@poly));\n+---------------------------------------+\n| NumInteriorRings(GeomFromText(@poly)) |\n+---------------------------------------+\n| 1 |\n+---------------------------------------+\n\nNon-overlapping 'polygon':\n\nSELECT ST_NumInteriorRings(ST_PolyFromText('POLYGON((0 0,10 0,10 10,0 10,0 0),\n (-1 -1,-5 -1,-5 -5,-1 -5,-1 -1))')) AS NumInteriorRings;\n+------------------+\n| NumInteriorRings |\n+------------------+\n| 1 |\n+------------------+\n\nURL: https://mariadb.com/kb/en/st_numinteriorrings/ [ST_OVERLAPS] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether geometry g1 spatially\noverlaps geometry g2.\n \nThe term spatially overlaps is used if two geometries\nintersect and their\nintersection results in a geometry of the same dimension but\nnot equal to\neither of the given geometries.\n \nST_OVERLAPS() uses object shapes, while OVERLAPS(), based on\nthe original MySQL implementation, uses object bounding\nrectangles. -[ST_POINTFROMTEXT] -declaration=wkt[,srid] -category=WKT -description=Constructs a POINT value using its WKT representation and\nSRID.\n \nST_PointFromText() and PointFromText() are synonyms.\n \n\nCREATE TABLE gis_point (g POINT);\nSHOW FIELDS FROM gis_point;\n \nINSERT INTO gis_point VALUES\n (PointFromText(''POINT(10 10)'')),\n (PointFromText(''POINT(20 10)'')),\n (PointFromText(''POINT(20 20)'')),\n (PointFromWKB(AsWKB(PointFromText(''POINT(10 20)'')))); -[ST_POINTFROMWKB] -declaration=wkb[,srid] -category=WKB -description=Constructs a POINT value using its WKB representation and\nSRID.\n \nST_PointFromWKB() and PointFromWKB() are synonyms.\n \n\nSET @g = ST_AsBinary(ST_PointFromText(''POINT(0 4)''));\n \nSELECT ST_AsText(ST_PointFromWKB(@g)) AS p;\n \n+------------+\n| p |\n+------------+\n| POINT(0 4) |\n+------------+ +description=Returns 1 or 0 to indicate whether geometry g1 spatially overlaps geometry g2.\n\nThe term spatially overlaps is used if two geometries intersect and their\nintersection results in a geometry of the same dimension but not equal to\neither of the given geometries.\n\nST_OVERLAPS() uses object shapes, while OVERLAPS(), based on the original\nMySQL implementation, uses object bounding rectangles.\n\nURL: https://mariadb.com/kb/en/st-overlaps/ [ST_POINTN] declaration=ls,N category=LineString Properties -description=Returns the N-th Point in the LineString value ls.\nPoints are numbered beginning with 1.\n \nST_PointN() and PointN() are synonyms.\n \n\nSET @ls = ''LineString(1 1,2 2,3 3)'';\n \nSELECT AsText(PointN(GeomFromText(@ls),2));\n+-------------------------------------+\n| AsText(PointN(GeomFromText(@ls),2)) |\n+-------------------------------------+\n| POINT(2 2) |\n+-------------------------------------+ +description=Returns the N-th Point in the LineString value ls. Points are numbered\nbeginning with 1.\n\nST_PointN() and PointN() are synonyms.\n\nExamples\n--------\n\nSET @ls = 'LineString(1 1,2 2,3 3)';\n\nSELECT AsText(PointN(GeomFromText(@ls),2));\n+-------------------------------------+\n| AsText(PointN(GeomFromText(@ls),2)) |\n+-------------------------------------+\n| POINT(2 2) |\n+-------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_pointn/ [ST_POINTONSURFACE] -declaration=g +declaration= category=Geometry Constructors -description=Given a geometry, returns a POINT guaranteed to intersect a\nsurface. However, see MDEV-7514.\n \nST_PointOnSurface() and PointOnSurface() are synonyms. -[ST_POLYFROMTEXT] +description=Given a geometry, returns a POINT guaranteed to intersect a surface. However,\nsee MDEV-7514.\n\nST_PointOnSurface() and PointOnSurface() are synonyms.\n\nURL: https://mariadb.com/kb/en/st_pointonsurface/ +[ST_PointFromText] +declaration=wkt[,srid] +category=WKT +description=Constructs a POINT value using its WKT representation and SRID.\n\nST_PointFromText() and PointFromText() are synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_point (g POINT);\nSHOW FIELDS FROM gis_point;\nINSERT INTO gis_point VALUES\n (PointFromText('POINT(10 10)')),\n (PointFromText('POINT(20 10)')),\n (PointFromText('POINT(20 20)')),\n (PointFromWKB(AsWKB(PointFromText('POINT(10 20)'))));\n\nURL: https://mariadb.com/kb/en/st_pointfromtext/ +[ST_PointFromWKB] +declaration=wkb[,srid] +category=WKB +description=Constructs a POINT value using its WKB representation and SRID.\n\nST_PointFromWKB() and PointFromWKB() are synonyms.\n\nExamples\n--------\n\nSET @g = ST_AsBinary(ST_PointFromText('POINT(0 4)'));\n\nSELECT ST_AsText(ST_PointFromWKB(@g)) AS p;\n+------------+\n| p |\n+------------+\n| POINT(0 4) |\n+------------+\n\nURL: https://mariadb.com/kb/en/st_pointfromwkb/ +[ST_PolyFromText] declaration=wkt[,srid] category=WKT -description=Constructs a POLYGON value using its WKT representation and\nSRID.\n \nST_PolyFromText(), ST_PolygonFromText(), PolyFromText() and\nST_PolygonFromText() are all synonyms.\n \n\nCREATE TABLE gis_polygon (g POLYGON);\nINSERT INTO gis_polygon VALUES\n (PolygonFromText(''POLYGON((10 10,20 10,20 20,10 20,10\n10))'')),\n (PolyFromText(''POLYGON((0 0,50 0,50 50,0 50,0 0), (10\n10,20 10,20 20,10 20,10 10))'')); -[ST_POLYFROMWKB] +description=Constructs a POLYGON value using its WKT representation and SRID.\n\nST_PolyFromText(), ST_PolygonFromText(), PolyFromText() and\nST_PolygonFromText() are all synonyms.\n\nExamples\n--------\n\nCREATE TABLE gis_polygon (g POLYGON);\nINSERT INTO gis_polygon VALUES\n (PolygonFromText('POLYGON((10 10,20 10,20 20,10 20,10 10))')),\n (PolyFromText('POLYGON((0 0,50 0,50 50,0 50,0 0), (10 10,20 10,20 20,10\n20,10 10))'));\n\nURL: https://mariadb.com/kb/en/st_polyfromtext/ +[ST_PolyFromWKB] declaration=wkb[,srid] category=WKB -description=Constructs a POLYGON value using its WKB representation and\nSRID.\n \nST_PolyFromWKB(), ST_PolygonFromWKB(), PolyFromWKB() and\nPolygonFromWKB() are synonyms.\n \n\nSET @g = ST_AsBinary(ST_PolyFromText(''POLYGON((1 1,1 5,4\n9,6 9,9 3,7 2,1 1))''));\n \nSELECT ST_AsText(ST_PolyFromWKB(@g)) AS p;\n \n+----------------------------------------+\n| p |\n+----------------------------------------+\n| POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1)) |\n+----------------------------------------+ +description=Constructs a POLYGON value using its WKB representation and SRID.\n\nST_PolyFromWKB(), ST_PolygonFromWKB(), PolyFromWKB() and PolygonFromWKB() are\nsynonyms.\n\nExamples\n--------\n\nSET @g = ST_AsBinary(ST_PolyFromText('POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1\n1))'));\n\nSELECT ST_AsText(ST_PolyFromWKB(@g)) AS p;\n+----------------------------------------+\n| p |\n+----------------------------------------+\n| POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1)) |\n+----------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_polyfromwkb/ [ST_RELATE] -declaration=g1, g2, i +declaration= category=Geometry Properties -description=Returns true if Geometry g1 is spatially related to\nGeometryg2 by testing for intersections between the\ninterior, boundary and exterior of the two geometries as\nspecified by the values in intersection matrix pattern i. +description=Returns true if Geometry g1 is spatially related to Geometryg2 by testing for\nintersections between the interior, boundary and exterior of the two\ngeometries as specified by the values in intersection matrix pattern i.\n\nURL: https://mariadb.com/kb/en/st_relate/ [ST_SRID] declaration=g category=Geometry Properties -description=Returns an integer indicating the Spatial Reference System\nID for the\ngeometry value g.\n \nIn MariaDB, the SRID value is just an integer associated\nwith the\ngeometry value. All calculations are done assuming Euclidean\n(planar)\ngeometry.\n \nST_SRID() and SRID() are synonyms.\n \n\nSELECT SRID(GeomFromText(''LineString(1 1,2 2)'',101));\n+-----------------------------------------------+\n| SRID(GeomFromText(''LineString(1 1,2 2)'',101)) |\n+-----------------------------------------------+\n| 101 |\n+-----------------------------------------------+ +description=Returns an integer indicating the Spatial Reference System ID for the geometry\nvalue g.\n\nIn MariaDB, the SRID value is just an integer associated with the geometry\nvalue. All calculations are done assuming Euclidean (planar) geometry.\n\nST_SRID() and SRID() are synonyms.\n\nExamples\n--------\n\nSELECT SRID(GeomFromText('LineString(1 1,2 2)',101));\n+-----------------------------------------------+\n| SRID(GeomFromText('LineString(1 1,2 2)',101)) |\n+-----------------------------------------------+\n| 101 |\n+-----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_srid/ +[ST_STARTPOINT] +declaration=ls +category=LineString Properties +description=Returns the Point that is the start point of the LineString value ls.\n\nST_StartPoint() and StartPoint() are synonyms.\n\nExamples\n--------\n\nSET @ls = 'LineString(1 1,2 2,3 3)';\n\nSELECT AsText(StartPoint(GeomFromText(@ls)));\n+---------------------------------------+\n| AsText(StartPoint(GeomFromText(@ls))) |\n+---------------------------------------+\n| POINT(1 1) |\n+---------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_startpoint/ [ST_SYMDIFFERENCE] declaration=g1,g2 category=Geometry Constructors -description=Returns a geometry that represents the portions of geometry\ng1 and geometry g2 that don''t intersect.\n \n\nSET @g1 = ST_GEOMFROMTEXT(''LINESTRING(10 20, 10 40)'');\n \nSET @g2 = ST_GEOMFROMTEXT(''LINESTRING(10 15, 10 25)'');\n \nSELECT ASTEXT(ST_SYMDIFFERENCE(@g1,@g2));\n+----------------------------------------------+\n| ASTEXT(ST_SYMDIFFERENCE(@g1,@g2)) |\n+----------------------------------------------+\n| MULTILINESTRING((10 15,10 20),(10 25,10 40)) |\n+----------------------------------------------+\n \nSET @g2 = ST_GeomFromText(''LINESTRING(10 20, 10 41)'');\n \nSELECT ASTEXT(ST_SYMDIFFERENCE(@g1,@g2));\n+-----------------------------------+\n| ASTEXT(ST_SYMDIFFERENCE(@g1,@g2)) |\n+-----------------------------------+\n| LINESTRING(10 40,10 41) |\n+-----------------------------------+ +description=Returns a geometry that represents the portions of geometry g1 and geometry g2\nthat don't intersect.\n\nExamples\n--------\n\nSET @g1 = ST_GEOMFROMTEXT('LINESTRING(10 20, 10 40)');\n\nSET @g2 = ST_GEOMFROMTEXT('LINESTRING(10 15, 10 25)');\n\nSELECT ASTEXT(ST_SYMDIFFERENCE(@g1,@g2));\n+----------------------------------------------+\n| ASTEXT(ST_SYMDIFFERENCE(@g1,@g2)) |\n+----------------------------------------------+\n| MULTILINESTRING((10 15,10 20),(10 25,10 40)) |\n+----------------------------------------------+\n\nSET @g2 = ST_GeomFromText('LINESTRING(10 20, 10 41)');\n\nSELECT ASTEXT(ST_SYMDIFFERENCE(@g1,@g2));\n+-----------------------------------+\n| ASTEXT(ST_SYMDIFFERENCE(@g1,@g2)) |\n+-----------------------------------+\n| LINESTRING(10 40,10 41) |\n+-----------------------------------+\n\nURL: https://mariadb.com/kb/en/st_symdifference/ [ST_TOUCHES] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether geometry g1 spatially\ntouches geometry g2. Two geometries spatially touch if the\ninteriors of the geometries do not intersect,\nbut the boundary of one of the geometries intersects either\nthe boundary or the\ninterior of the other.\n \nST_TOUCHES() uses object shapes, while TOUCHES(), based on\nthe original MySQL implementation, uses object bounding\nrectangles.\n \n\nSET @g1 = ST_GEOMFROMTEXT(''POINT(2 0)'');\n \nSET @g2 = ST_GEOMFROMTEXT(''LINESTRING(2 0, 0 2)'');\n \nSELECT ST_TOUCHES(@g1,@g2);\n+---------------------+\n| ST_TOUCHES(@g1,@g2) |\n+---------------------+\n| 1 |\n+---------------------+\n \nSET @g1 = ST_GEOMFROMTEXT(''POINT(2 1)'');\n \nSELECT ST_TOUCHES(@g1,@g2);\n+---------------------+\n| ST_TOUCHES(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+ +description=Returns 1 or 0 to indicate whether geometry g1 spatially touches geometry g2.\nTwo geometries spatially touch if the interiors of the geometries do not\nintersect, but the boundary of one of the geometries intersects either the\nboundary or the interior of the other.\n\nST_TOUCHES() uses object shapes, while TOUCHES(), based on the original MySQL\nimplementation, uses object bounding rectangles.\n\nExamples\n--------\n\nSET @g1 = ST_GEOMFROMTEXT('POINT(2 0)');\n\nSET @g2 = ST_GEOMFROMTEXT('LINESTRING(2 0, 0 2)');\n\nSELECT ST_TOUCHES(@g1,@g2);\n+---------------------+\n| ST_TOUCHES(@g1,@g2) |\n+---------------------+\n| 1 |\n+---------------------+\n\nSET @g1 = ST_GEOMFROMTEXT('POINT(2 1)');\n\nSELECT ST_TOUCHES(@g1,@g2);\n+---------------------+\n| ST_TOUCHES(@g1,@g2) |\n+---------------------+\n| 0 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/st-touches/ [ST_UNION] declaration=g1,g2 category=Geometry Constructors -description=Returns a geometry that is the union of the geometry g1 and\ngeometry g2.\n \n\nSET @g1 = GEOMFROMTEXT(''POINT (0 2)'');\n \nSET @g2 = GEOMFROMTEXT(''POINT (2 0)'');\n \nSELECT ASTEXT(ST_UNION(@g1,@g2));\n+---------------------------+\n| ASTEXT(ST_UNION(@g1,@g2)) |\n+---------------------------+\n| MULTIPOINT(2 0,0 2) |\n+---------------------------+\n \nSET @g1 = GEOMFROMTEXT(''POLYGON((0 0,0 3,3 3,3 0,0 0))'');\n \nSET @g2 = GEOMFROMTEXT(''POLYGON((2 2,4 2,4 4,2 4,2 2))'');\n \nSELECT ASTEXT(ST_UNION(@g1,@g2));\n+------------------------------------------------+\n| ASTEXT(ST_UNION(@g1,@g2)) |\n+------------------------------------------------+\n| POLYGON((0 0,0 3,2 3,2 4,4 4,4 2,3 2,3 0,0 0)) |\n+------------------------------------------------+ +description=Returns a geometry that is the union of the geometry g1 and geometry g2.\n\nExamples\n--------\n\nSET @g1 = GEOMFROMTEXT('POINT (0 2)');\n\nSET @g2 = GEOMFROMTEXT('POINT (2 0)');\n\nSELECT ASTEXT(ST_UNION(@g1,@g2));\n+---------------------------+\n| ASTEXT(ST_UNION(@g1,@g2)) |\n+---------------------------+\n| MULTIPOINT(2 0,0 2) |\n+---------------------------+\n\nSET @g1 = GEOMFROMTEXT('POLYGON((0 0,0 3,3 3,3 0,0 0))');\n\nSET @g2 = GEOMFROMTEXT('POLYGON((2 2,4 2,4 4,2 4,2 2))');\n\nSELECT ASTEXT(ST_UNION(@g1,@g2));\n+------------------------------------------------+\n| ASTEXT(ST_UNION(@g1,@g2)) |\n+------------------------------------------------+\n| POLYGON((0 0,0 3,2 3,2 4,4 4,4 2,3 2,3 0,0 0)) |\n+------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/st_union/ [ST_WITHIN] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether geometry g1 is spatially\nwithin geometry g2.\n \nThis tests the opposite relationship as ST_CONTAINS().\n \nST_WITHIN() uses object shapes, while WITHIN(), based on the\noriginal MySQL implementation, uses object bounding\nrectangles.\n \n\nSET @g1 = ST_GEOMFROMTEXT(''POINT(174 149)'');\n \nSET @g2 = ST_GEOMFROMTEXT(''POLYGON((175 150, 20 40, 50 60,\n125 100, 175 150))'');\n \nSELECT ST_WITHIN(@g1,@g2);\n+--------------------+\n| ST_WITHIN(@g1,@g2) |\n+--------------------+\n| 1 |\n+--------------------+\n \nSET @g1 = ST_GEOMFROMTEXT(''POINT(176 151)'');\n \nSELECT ST_WITHIN(@g1,@g2);\n+--------------------+\n| ST_WITHIN(@g1,@g2) |\n+--------------------+\n| 0 |\n+--------------------+ +description=Returns 1 or 0 to indicate whether geometry g1 is spatially within geometry g2.\n\nThis tests the opposite relationship as ST_CONTAINS().\n\nST_WITHIN() uses object shapes, while WITHIN(), based on the original MySQL\nimplementation, uses object bounding rectangles.\n\nExamples\n--------\n\nSET @g1 = ST_GEOMFROMTEXT('POINT(174 149)');\n\nSET @g2 = ST_GEOMFROMTEXT('POLYGON((175 150, 20 40, 50 60, 125 100, 175\n150))');\n\nSELECT ST_WITHIN(@g1,@g2);\n+--------------------+\n| ST_WITHIN(@g1,@g2) |\n+--------------------+\n| 1 |\n+--------------------+\n\nSET @g1 = ST_GEOMFROMTEXT('POINT(176 151)');\n\nSELECT ST_WITHIN(@g1,@g2);\n+--------------------+\n| ST_WITHIN(@g1,@g2) |\n+--------------------+\n| 0 |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/st-within/ [ST_X] declaration=p category=Point Properties -description=Returns the X-coordinate value for the point p as a\ndouble-precision number.\n \nST_X() and X() are synonyms.\n \n\nSET @pt = ''Point(56.7 53.34)'';\n \nSELECT X(GeomFromText(@pt));\n+----------------------+\n| X(GeomFromText(@pt)) |\n+----------------------+\n| 56.7 |\n+----------------------+ +description=Returns the X-coordinate value for the point p as a double-precision number.\n\nST_X() and X() are synonyms.\n\nExamples\n--------\n\nSET @pt = 'Point(56.7 53.34)';\n\nSELECT X(GeomFromText(@pt));\n+----------------------+\n| X(GeomFromText(@pt)) |\n+----------------------+\n| 56.7 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/st_x/ [ST_Y] declaration=p category=Point Properties -description=Returns the Y-coordinate value for the point p as a\ndouble-precision number.\n \nST_Y() and Y() are synonyms.\n \n\nSET @pt = ''Point(56.7 53.34)'';\n \nSELECT Y(GeomFromText(@pt));\n+----------------------+\n| Y(GeomFromText(@pt)) |\n+----------------------+\n| 53.34 |\n+----------------------+ -[SUBDATE1] -name=SUBDATE +description=Returns the Y-coordinate value for the point p as a double-precision number.\n\nST_Y() and Y() are synonyms.\n\nExamples\n--------\n\nSET @pt = 'Point(56.7 53.34)';\n\nSELECT Y(GeomFromText(@pt));\n+----------------------+\n| Y(GeomFromText(@pt)) |\n+----------------------+\n| 53.34 |\n+----------------------+\n\nURL: https://mariadb.com/kb/en/st_y/ +[SUBDATE] declaration=date,INTERVAL expr unit category=Date and Time Functions -description=When invoked with the INTERVAL form of the second argument,\nSUBDATE()\nis a synonym for DATE_SUB(). See Date and Time Units for a\ncomplete list of permitted units. \n \nThe second form allows the use of an integer value for days.\nIn such\ncases, it is interpreted as the number of days to be\nsubtracted from\nthe date or datetime expression expr.\n \n\nSELECT DATE_SUB(''2008-01-02'', INTERVAL 31 DAY);\n+-----------------------------------------+\n| DATE_SUB(''2008-01-02'', INTERVAL 31 DAY) |\n+-----------------------------------------+\n| 2007-12-02 |\n+-----------------------------------------+\n \nSELECT SUBDATE(''2008-01-02'', INTERVAL 31 DAY);\n+----------------------------------------+\n| SUBDATE(''2008-01-02'', INTERVAL 31 DAY) |\n+----------------------------------------+\n| 2007-12-02 |\n+----------------------------------------+\n \nSELECT SUBDATE(''2008-01-02 12:00:00'', 31);\n+------------------------------------+\n| SUBDATE(''2008-01-02 12:00:00'', 31) |\n+------------------------------------+\n| 2007-12-02 12:00:00 |\n+------------------------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT d, SUBDATE(d, 10) from t1;\n \n+---------------------+---------------------+\n| d | SUBDATE(d, 10) |\n+---------------------+---------------------+\n| 2007-01-30 21:31:07 | 2007-01-20 21:31:07 |\n| 1983-10-15 06:42:51 | 1983-10-05 06:42:51 |\n| 2011-04-21 12:34:56 | 2011-04-11 12:34:56 |\n| 2011-10-30 06:31:41 | 2011-10-20 06:31:41 |\n| 2011-01-30 14:03:25 | 2011-01-20 14:03:25 |\n| 2004-10-07 11:19:34 | 2004-09-27 11:19:34 |\n+---------------------+---------------------+\n \nSELECT d, SUBDATE(d, INTERVAL 10 MINUTE) from t1;\n \n+---------------------+--------------------------------+\n| d | SUBDATE(d, INTERVAL 10 MINUTE) |\n+---------------------+--------------------------------+\n| 2007-01-30 21:31:07 | 2007-01-30 21:21:07 |\n| 1983-10-15 06:42:51 | 1983-10-15 06:32:51 |\n| 2011-04-21 12:34:56 | 2011-04-21 12:24:56 |\n| 2011-10-30 06:31:41 | 2011-10-30 06:21:41 |\n| 2011-01-30 14:03:25 | 2011-01-30 13:53:25 |\n| 2004-10-07 11:19:34 | 2004-10-07 11:09:34 |\n+---------------------+--------------------------------+ -[SUBDATE2] -name=SUBDATE -declaration=expr,days -category=Date and Time Functions -description=When invoked with the INTERVAL form of the second argument,\nSUBDATE()\nis a synonym for DATE_SUB(). See Date and Time Units for a\ncomplete list of permitted units. \n \nThe second form allows the use of an integer value for days.\nIn such\ncases, it is interpreted as the number of days to be\nsubtracted from\nthe date or datetime expression expr.\n \n\nSELECT DATE_SUB(''2008-01-02'', INTERVAL 31 DAY);\n+-----------------------------------------+\n| DATE_SUB(''2008-01-02'', INTERVAL 31 DAY) |\n+-----------------------------------------+\n| 2007-12-02 |\n+-----------------------------------------+\n \nSELECT SUBDATE(''2008-01-02'', INTERVAL 31 DAY);\n+----------------------------------------+\n| SUBDATE(''2008-01-02'', INTERVAL 31 DAY) |\n+----------------------------------------+\n| 2007-12-02 |\n+----------------------------------------+\n \nSELECT SUBDATE(''2008-01-02 12:00:00'', 31);\n+------------------------------------+\n| SUBDATE(''2008-01-02 12:00:00'', 31) |\n+------------------------------------+\n| 2007-12-02 12:00:00 |\n+------------------------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT d, SUBDATE(d, 10) from t1;\n \n+---------------------+---------------------+\n| d | SUBDATE(d, 10) |\n+---------------------+---------------------+\n| 2007-01-30 21:31:07 | 2007-01-20 21:31:07 |\n| 1983-10-15 06:42:51 | 1983-10-05 06:42:51 |\n| 2011-04-21 12:34:56 | 2011-04-11 12:34:56 |\n| 2011-10-30 06:31:41 | 2011-10-20 06:31:41 |\n| 2011-01-30 14:03:25 | 2011-01-20 14:03:25 |\n| 2004-10-07 11:19:34 | 2004-09-27 11:19:34 |\n+---------------------+---------------------+\n \nSELECT d, SUBDATE(d, INTERVAL 10 MINUTE) from t1;\n \n+---------------------+--------------------------------+\n| d | SUBDATE(d, INTERVAL 10 MINUTE) |\n+---------------------+--------------------------------+\n| 2007-01-30 21:31:07 | 2007-01-30 21:21:07 |\n| 1983-10-15 06:42:51 | 1983-10-15 06:32:51 |\n| 2011-04-21 12:34:56 | 2011-04-21 12:24:56 |\n| 2011-10-30 06:31:41 | 2011-10-30 06:21:41 |\n| 2011-01-30 14:03:25 | 2011-01-30 13:53:25 |\n| 2004-10-07 11:19:34 | 2004-10-07 11:09:34 |\n+---------------------+--------------------------------+ -[SUBSTRING1] -name=SUBSTRING -declaration=str,pos -category=String Functions -description=The forms without a len argument return a substring from\nstring str starting at position pos.\n \nThe forms with a len argument return a substring len\ncharacters long from string str, starting at position pos.\n \nThe forms that use FROM are standard SQL syntax.\n \nIt is also possible to use a negative value for pos. In this\ncase, the beginning of the substring is pos characters from\nthe end of the string, rather than the beginning. A negative\nvalue may be used for pos in any of the forms of this\nfunction.\n \nBy default, the position of the first character in the\nstring from which the substring is to be extracted is\nreckoned as 1. For Oracle-compatibility, from MariaDB\n10.3.3, when sql_mode is set to ''oracle'', position zero is\ntreated as position 1 (although the first character is still\nreckoned as 1).\n \nIf any argument is NULL, returns NULL.\n \n\nSELECT SUBSTRING(''Knowledgebase'',5);\n+------------------------------+\n| SUBSTRING(''Knowledgebase'',5) |\n+------------------------------+\n| ledgebase |\n+------------------------------+\n \nSELECT SUBSTRING(''MariaDB'' FROM 6);\n+-----------------------------+\n| SUBSTRING(''MariaDB'' FROM 6) |\n+-----------------------------+\n| DB |\n+-----------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'',3,7);\n+--------------------------------+\n| SUBSTRING(''Knowledgebase'',3,7) |\n+--------------------------------+\n| owledge |\n+--------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'', -4);\n+--------------------------------+\n| SUBSTRING(''Knowledgebase'', -4) |\n+--------------------------------+\n| base |\n+--------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'', -8, 4);\n+-----------------------------------+\n| SUBSTRING(''Knowledgebase'', -8, 4) |\n+-----------------------------------+\n| edge |\n+-----------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'' FROM -8 FOR 4);\n+------------------------------------------+\n| SUBSTRING(''Knowledgebase'' FROM -8 FOR 4) |\n+------------------------------------------+\n| edge |\n+------------------------------------------+\n \nOracle mode from MariaDB 10.3.3:\n \nSELECT SUBSTR(''abc'',0,3);\n+-------------------+\n| SUBSTR(''abc'',0,3) |\n+-------------------+\n| |\n+-------------------+\n \nSELECT SUBSTR(''abc'',1,2);\n+-------------------+\n| SUBSTR(''abc'',1,2) |\n+-------------------+\n| ab |\n+-------------------+\n \nSET sql_mode=''oracle'';\n \nSELECT SUBSTR(''abc'',0,3);\n+-------------------+\n| SUBSTR(''abc'',0,3) |\n+-------------------+\n| abc |\n+-------------------+\n \nSELECT SUBSTR(''abc'',1,2);\n+-------------------+\n| SUBSTR(''abc'',1,2) |\n+-------------------+\n| ab |\n+-------------------+ -[SUBSTRING2] -name=SUBSTRING -declaration=str FROM pos -category=String Functions -description=The forms without a len argument return a substring from\nstring str starting at position pos.\n \nThe forms with a len argument return a substring len\ncharacters long from string str, starting at position pos.\n \nThe forms that use FROM are standard SQL syntax.\n \nIt is also possible to use a negative value for pos. In this\ncase, the beginning of the substring is pos characters from\nthe end of the string, rather than the beginning. A negative\nvalue may be used for pos in any of the forms of this\nfunction.\n \nBy default, the position of the first character in the\nstring from which the substring is to be extracted is\nreckoned as 1. For Oracle-compatibility, from MariaDB\n10.3.3, when sql_mode is set to ''oracle'', position zero is\ntreated as position 1 (although the first character is still\nreckoned as 1).\n \nIf any argument is NULL, returns NULL.\n \n\nSELECT SUBSTRING(''Knowledgebase'',5);\n+------------------------------+\n| SUBSTRING(''Knowledgebase'',5) |\n+------------------------------+\n| ledgebase |\n+------------------------------+\n \nSELECT SUBSTRING(''MariaDB'' FROM 6);\n+-----------------------------+\n| SUBSTRING(''MariaDB'' FROM 6) |\n+-----------------------------+\n| DB |\n+-----------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'',3,7);\n+--------------------------------+\n| SUBSTRING(''Knowledgebase'',3,7) |\n+--------------------------------+\n| owledge |\n+--------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'', -4);\n+--------------------------------+\n| SUBSTRING(''Knowledgebase'', -4) |\n+--------------------------------+\n| base |\n+--------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'', -8, 4);\n+-----------------------------------+\n| SUBSTRING(''Knowledgebase'', -8, 4) |\n+-----------------------------------+\n| edge |\n+-----------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'' FROM -8 FOR 4);\n+------------------------------------------+\n| SUBSTRING(''Knowledgebase'' FROM -8 FOR 4) |\n+------------------------------------------+\n| edge |\n+------------------------------------------+\n \nOracle mode from MariaDB 10.3.3:\n \nSELECT SUBSTR(''abc'',0,3);\n+-------------------+\n| SUBSTR(''abc'',0,3) |\n+-------------------+\n| |\n+-------------------+\n \nSELECT SUBSTR(''abc'',1,2);\n+-------------------+\n| SUBSTR(''abc'',1,2) |\n+-------------------+\n| ab |\n+-------------------+\n \nSET sql_mode=''oracle'';\n \nSELECT SUBSTR(''abc'',0,3);\n+-------------------+\n| SUBSTR(''abc'',0,3) |\n+-------------------+\n| abc |\n+-------------------+\n \nSELECT SUBSTR(''abc'',1,2);\n+-------------------+\n| SUBSTR(''abc'',1,2) |\n+-------------------+\n| ab |\n+-------------------+ -[SUBSTRING3] -name=SUBSTRING -declaration=str,pos,len +description=When invoked with the INTERVAL form of the second argument, SUBDATE() is a\nsynonym for DATE_SUB(). See Date and Time Units for a complete list of\npermitted units.\n\nThe second form allows the use of an integer value for days. In such cases, it\nis interpreted as the number of days to be subtracted from the date or\ndatetime expression expr.\n\nExamples\n--------\n\nSELECT DATE_SUB('2008-01-02', INTERVAL 31 DAY);\n+-----------------------------------------+\n| DATE_SUB('2008-01-02', INTERVAL 31 DAY) |\n+-----------------------------------------+\n| 2007-12-02 |\n+-----------------------------------------+\n\nSELECT SUBDATE('2008-01-02', INTERVAL 31 DAY);\n+----------------------------------------+\n| SUBDATE('2008-01-02', INTERVAL 31 DAY) |\n+----------------------------------------+\n| 2007-12-02 |\n+----------------------------------------+\n\nSELECT SUBDATE('2008-01-02 12:00:00', 31);\n+------------------------------------+\n| SUBDATE('2008-01-02 12:00:00', 31) |\n+------------------------------------+\n| 2007-12-02 12:00:00 |\n+------------------------------------+\n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n\nSELECT d, SUBDATE(d, 10) from t1;\n+---------------------+---------------------+\n| d | SUBDATE(d, 10) |\n+---------------------+---------------------+\n| 2007-01-30 21:31:07 | 2007-01-20 21:31:07 |\n| 1983-10-15 06:42:51 | 1983-10-05 06:42:51 |\n| 2011-04-21 12:34:56 | 2011-04-11 12:34:56 |\n| 2011-10-30 06:31:41 | 2011-10-20 06:31:41 |\n| 2011-01-30 14:03:25 | 2011-01-20 14:03:25 |\n ... +[SUBSTR] +declaration= category=String Functions -description=The forms without a len argument return a substring from\nstring str starting at position pos.\n \nThe forms with a len argument return a substring len\ncharacters long from string str, starting at position pos.\n \nThe forms that use FROM are standard SQL syntax.\n \nIt is also possible to use a negative value for pos. In this\ncase, the beginning of the substring is pos characters from\nthe end of the string, rather than the beginning. A negative\nvalue may be used for pos in any of the forms of this\nfunction.\n \nBy default, the position of the first character in the\nstring from which the substring is to be extracted is\nreckoned as 1. For Oracle-compatibility, from MariaDB\n10.3.3, when sql_mode is set to ''oracle'', position zero is\ntreated as position 1 (although the first character is still\nreckoned as 1).\n \nIf any argument is NULL, returns NULL.\n \n\nSELECT SUBSTRING(''Knowledgebase'',5);\n+------------------------------+\n| SUBSTRING(''Knowledgebase'',5) |\n+------------------------------+\n| ledgebase |\n+------------------------------+\n \nSELECT SUBSTRING(''MariaDB'' FROM 6);\n+-----------------------------+\n| SUBSTRING(''MariaDB'' FROM 6) |\n+-----------------------------+\n| DB |\n+-----------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'',3,7);\n+--------------------------------+\n| SUBSTRING(''Knowledgebase'',3,7) |\n+--------------------------------+\n| owledge |\n+--------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'', -4);\n+--------------------------------+\n| SUBSTRING(''Knowledgebase'', -4) |\n+--------------------------------+\n| base |\n+--------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'', -8, 4);\n+-----------------------------------+\n| SUBSTRING(''Knowledgebase'', -8, 4) |\n+-----------------------------------+\n| edge |\n+-----------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'' FROM -8 FOR 4);\n+------------------------------------------+\n| SUBSTRING(''Knowledgebase'' FROM -8 FOR 4) |\n+------------------------------------------+\n| edge |\n+------------------------------------------+\n \nOracle mode from MariaDB 10.3.3:\n \nSELECT SUBSTR(''abc'',0,3);\n+-------------------+\n| SUBSTR(''abc'',0,3) |\n+-------------------+\n| |\n+-------------------+\n \nSELECT SUBSTR(''abc'',1,2);\n+-------------------+\n| SUBSTR(''abc'',1,2) |\n+-------------------+\n| ab |\n+-------------------+\n \nSET sql_mode=''oracle'';\n \nSELECT SUBSTR(''abc'',0,3);\n+-------------------+\n| SUBSTR(''abc'',0,3) |\n+-------------------+\n| abc |\n+-------------------+\n \nSELECT SUBSTR(''abc'',1,2);\n+-------------------+\n| SUBSTR(''abc'',1,2) |\n+-------------------+\n| ab |\n+-------------------+ -[SUBSTRING4] -name=SUBSTRING -declaration=str FROM pos FOR len +description=URL: https://mariadb.com/kb/en/substr/ +[SUBSTRING] +declaration=str,pos category=String Functions -description=The forms without a len argument return a substring from\nstring str starting at position pos.\n \nThe forms with a len argument return a substring len\ncharacters long from string str, starting at position pos.\n \nThe forms that use FROM are standard SQL syntax.\n \nIt is also possible to use a negative value for pos. In this\ncase, the beginning of the substring is pos characters from\nthe end of the string, rather than the beginning. A negative\nvalue may be used for pos in any of the forms of this\nfunction.\n \nBy default, the position of the first character in the\nstring from which the substring is to be extracted is\nreckoned as 1. For Oracle-compatibility, from MariaDB\n10.3.3, when sql_mode is set to ''oracle'', position zero is\ntreated as position 1 (although the first character is still\nreckoned as 1).\n \nIf any argument is NULL, returns NULL.\n \n\nSELECT SUBSTRING(''Knowledgebase'',5);\n+------------------------------+\n| SUBSTRING(''Knowledgebase'',5) |\n+------------------------------+\n| ledgebase |\n+------------------------------+\n \nSELECT SUBSTRING(''MariaDB'' FROM 6);\n+-----------------------------+\n| SUBSTRING(''MariaDB'' FROM 6) |\n+-----------------------------+\n| DB |\n+-----------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'',3,7);\n+--------------------------------+\n| SUBSTRING(''Knowledgebase'',3,7) |\n+--------------------------------+\n| owledge |\n+--------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'', -4);\n+--------------------------------+\n| SUBSTRING(''Knowledgebase'', -4) |\n+--------------------------------+\n| base |\n+--------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'', -8, 4);\n+-----------------------------------+\n| SUBSTRING(''Knowledgebase'', -8, 4) |\n+-----------------------------------+\n| edge |\n+-----------------------------------+\n \nSELECT SUBSTRING(''Knowledgebase'' FROM -8 FOR 4);\n+------------------------------------------+\n| SUBSTRING(''Knowledgebase'' FROM -8 FOR 4) |\n+------------------------------------------+\n| edge |\n+------------------------------------------+\n \nOracle mode from MariaDB 10.3.3:\n \nSELECT SUBSTR(''abc'',0,3);\n+-------------------+\n| SUBSTR(''abc'',0,3) |\n+-------------------+\n| |\n+-------------------+\n \nSELECT SUBSTR(''abc'',1,2);\n+-------------------+\n| SUBSTR(''abc'',1,2) |\n+-------------------+\n| ab |\n+-------------------+\n \nSET sql_mode=''oracle'';\n \nSELECT SUBSTR(''abc'',0,3);\n+-------------------+\n| SUBSTR(''abc'',0,3) |\n+-------------------+\n| abc |\n+-------------------+\n \nSELECT SUBSTR(''abc'',1,2);\n+-------------------+\n| SUBSTR(''abc'',1,2) |\n+-------------------+\n| ab |\n+-------------------+ +description=The forms without a len argument return a substring from string str starting\nat position pos.\n\nThe forms with a len argument return a substring len characters long from\nstring str, starting at position pos.\n\nThe forms that use FROM are standard SQL syntax.\n\nIt is also possible to use a negative value for pos. In this case, the\nbeginning of the substring is pos characters from the end of the string,\nrather than the beginning. A negative value may be used for pos in any of the\nforms of this function.\n\nBy default, the position of the first character in the string from which the\nsubstring is to be extracted is reckoned as 1. For Oracle-compatibility, from\nMariaDB 10.3.3, when sql_mode is set to 'oracle', position zero is treated as\nposition 1 (although the first character is still reckoned as 1).\n\nIf any argument is NULL, returns NULL.\n\nExamples\n--------\n\nSELECT SUBSTRING('Knowledgebase',5);\n+------------------------------+\n| SUBSTRING('Knowledgebase',5) |\n+------------------------------+\n| ledgebase |\n+------------------------------+\n\nSELECT SUBSTRING('MariaDB' FROM 6);\n+-----------------------------+\n| SUBSTRING('MariaDB' FROM 6) |\n+-----------------------------+\n| DB |\n+-----------------------------+\n\nSELECT SUBSTRING('Knowledgebase',3,7);\n+--------------------------------+\n| SUBSTRING('Knowledgebase',3,7) |\n+--------------------------------+\n| owledge |\n+--------------------------------+\n\nSELECT SUBSTRING('Knowledgebase', -4);\n+--------------------------------+\n| SUBSTRING('Knowledgebase', -4) |\n+--------------------------------+\n| base |\n+--------------------------------+\n ... [SUBSTRING_INDEX] declaration=str,delim,count category=String Functions -description=Returns the substring from string str before count\noccurrences of the\ndelimiter delim. If count is positive, everything to the\nleft\nof the final delimiter (counting from the left) is returned.\nIf count\nis negative, everything to the right of the final delimiter\n(counting from the\nright) is returned. SUBSTRING_INDEX() performs a\ncase-sensitive match when\nsearching for delim.\n \nIf any argument is NULL, returns NULL.\n \n\nSELECT SUBSTRING_INDEX(''www.mariadb.org'', ''.'', 2);\n+--------------------------------------------+\n| SUBSTRING_INDEX(''www.mariadb.org'', ''.'', 2) |\n+--------------------------------------------+\n| www.mariadb |\n+--------------------------------------------+\n \nSELECT SUBSTRING_INDEX(''www.mariadb.org'', ''.'', -2);\n+---------------------------------------------+\n| SUBSTRING_INDEX(''www.mariadb.org'', ''.'', -2) |\n+---------------------------------------------+\n| mariadb.org |\n+---------------------------------------------+ +description=Returns the substring from string str before count occurrences of the\ndelimiter delim. If count is positive, everything to the left of the final\ndelimiter (counting from the left) is returned. If count is negative,\neverything to the right of the final delimiter (counting from the right) is\nreturned. SUBSTRING_INDEX() performs a case-sensitive match when searching for\ndelim.\n\nIf any argument is NULL, returns NULL.\n\nFor example\n\nSUBSTRING_INDEX('www.mariadb.org', '.', 2)\n\nmeans "Return all of the characters up to the 2nd occurrence of ."\n\nExamples\n--------\n\nSELECT SUBSTRING_INDEX('www.mariadb.org', '.', 2);\n+--------------------------------------------+\n| SUBSTRING_INDEX('www.mariadb.org', '.', 2) |\n+--------------------------------------------+\n| www.mariadb |\n+--------------------------------------------+\n\nSELECT SUBSTRING_INDEX('www.mariadb.org', '.', -2);\n+---------------------------------------------+\n| SUBSTRING_INDEX('www.mariadb.org', '.', -2) |\n+---------------------------------------------+\n| mariadb.org |\n+---------------------------------------------+\n\nURL: https://mariadb.com/kb/en/substring_index/ [SUBTIME] declaration=expr1,expr2 category=Date and Time Functions -description=SUBTIME() returns expr1 - expr2 expressed as a value in the\nsame\nformat as expr1. expr1 is a time or datetime expression, and\nexpr2 is\na time expression.\n \n\nSELECT SUBTIME(''2007-12-31 23:59:59.999999'',''1\n1:1:1.000002'');\n+--------------------------------------------------------+\n| SUBTIME(''2007-12-31 23:59:59.999999'',''1 1:1:1.000002'')\n|\n+--------------------------------------------------------+\n| 2007-12-30 22:58:58.999997 |\n+--------------------------------------------------------+\n \nSELECT SUBTIME(''01:00:00.999999'', ''02:00:00.999998'');\n+-----------------------------------------------+\n| SUBTIME(''01:00:00.999999'', ''02:00:00.999998'') |\n+-----------------------------------------------+\n| -00:59:59.999999 |\n+-----------------------------------------------+ +description=SUBTIME() returns expr1 - expr2 expressed as a value in the same format as\nexpr1. expr1 is a time or datetime expression, and expr2 is a time expression.\n\nExamples\n--------\n\nSELECT SUBTIME('2007-12-31 23:59:59.999999','1 1:1:1.000002');\n+--------------------------------------------------------+\n| SUBTIME('2007-12-31 23:59:59.999999','1 1:1:1.000002') |\n+--------------------------------------------------------+\n| 2007-12-30 22:58:58.999997 |\n+--------------------------------------------------------+\n\nSELECT SUBTIME('01:00:00.999999', '02:00:00.999998');\n+-----------------------------------------------+\n| SUBTIME('01:00:00.999999', '02:00:00.999998') |\n+-----------------------------------------------+\n| -00:59:59.999999 |\n+-----------------------------------------------+\n\nURL: https://mariadb.com/kb/en/subtime/ [SUM] declaration=[DISTINCT] expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the sum of expr. If the return set has no rows,\nSUM() returns\nNULL. The DISTINCT keyword can be used to sum only the\ndistinct values\nof expr.\n \nFrom MariaDB 10.2.0, SUM() can be used as a window function,\nalthough not with the DISTINCT specifier.\n \n\nCREATE TABLE sales (sales_value INT);\nINSERT INTO sales VALUES(10),(20),(20),(40);\n \nSELECT SUM(sales_value) FROM sales;\n \n+------------------+\n| SUM(sales_value) |\n+------------------+\n| 90 |\n+------------------+\n \nSELECT SUM(DISTINCT(sales_value)) FROM sales;\n \n+----------------------------+\n| SUM(DISTINCT(sales_value)) |\n+----------------------------+\n| 70 |\n+----------------------------+\n \nCommonly, SUM is used with a GROUP BY clause:\n \nCREATE TABLE sales (name CHAR(10), month CHAR(10), units\nINT);\n \nINSERT INTO sales VALUES \n (''Chun'', ''Jan'', 75), (''Chun'', ''Feb'', 73),\n (''Esben'', ''Jan'', 43), (''Esben'', ''Feb'', 31),\n (''Kaolin'', ''Jan'', 56), (''Kaolin'', ''Feb'', 88),\n (''Tatiana'', ''Jan'', 87), (''Tatiana'', ''Feb'', 83);\n \nSELECT name, SUM(units) FROM sales GROUP BY name;\n \n+---------+------------+\n| name | SUM(units) |\n+---------+------------+\n| Chun | 148 |\n| Esben | 74 |\n| Kaolin | 144 |\n| Tatiana | 170 |\n+---------+------------+\n \nThe GROUP BY clause is required when using an aggregate\nfunction along with regular column data, otherwise the\nresult will be a mismatch, as in the following common type\nof mistake:\n \nSELECT name,SUM(units) FROM sales\n;\n+------+------------+\n| name | SUM(units) |\n+------+------------+\n| Chun | 536 |\n+------+------------+\n \nAs a window function:\n \nCREATE OR REPLACE TABLE student_test (name CHAR(10), test\nCHAR(10), score TINYINT);\nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87);\n \nSELECT name, test, score, SUM(score) OVER (PARTITION BY\nname) AS total_score FROM student_test;\n \n+---------+--------+-------+-------------+\n| name | test | score | total_score |\n+---------+--------+-------+-------------+\n| Chun | SQL | 75 | 148 |\n| Chun | Tuning | 73 | 148 |\n| Esben | SQL | 43 | 74 |\n| Esben | Tuning | 31 | 74 |\n| Kaolin | SQL | 56 | 144 |\n| Kaolin | Tuning | 88 | 144 |\n| Tatiana | SQL | 87 | 87 |\n+---------+--------+-------+-------------+ +description=Returns the sum of expr. If the return set has no rows, SUM() returns NULL.\nThe DISTINCT keyword can be used to sum only the distinct values of expr.\n\nSUM() can be used as a window function, although not with the DISTINCT\nspecifier.\n\nExamples\n--------\n\nCREATE TABLE sales (sales_value INT);\nINSERT INTO sales VALUES(10),(20),(20),(40);\n\nSELECT SUM(sales_value) FROM sales;\n+------------------+\n| SUM(sales_value) |\n+------------------+\n| 90 |\n+------------------+\n\nSELECT SUM(DISTINCT(sales_value)) FROM sales;\n+----------------------------+\n| SUM(DISTINCT(sales_value)) |\n+----------------------------+\n| 70 |\n+----------------------------+\n\nCommonly, SUM is used with a GROUP BY clause:\n\nCREATE TABLE sales (name CHAR(10), month CHAR(10), units INT);\n\nINSERT INTO sales VALUES \n ('Chun', 'Jan', 75), ('Chun', 'Feb', 73),\n ('Esben', 'Jan', 43), ('Esben', 'Feb', 31),\n ('Kaolin', 'Jan', 56), ('Kaolin', 'Feb', 88),\n ('Tatiana', 'Jan', 87), ('Tatiana', 'Feb', 83);\n\nSELECT name, SUM(units) FROM sales GROUP BY name;\n+---------+------------+\n| name | SUM(units) |\n+---------+------------+\n| Chun | 148 |\n| Esben | 74 |\n| Kaolin | 144 |\n| Tatiana | 170 |\n+---------+------------+\n\nThe GROUP BY clause is required when using an aggregate function along with\nregular column data, otherwise the result will be a mismatch, as in the\nfollowing common type of mistake:\n\n ... [SYSDATE] declaration=[precision] category=Date and Time Functions -description=Returns the current date and time as a value in ''YYYY-MM-DD\nHH:MM:SS''\nor YYYYMMDDHHMMSS.uuuuuu format, depending on whether the\nfunction is\nused in a string or numeric context.\n \nThe optional precision determines the microsecond precision.\nSee Microseconds in MariaDB.\n \nSYSDATE() returns the time at which it executes. This\ndiffers from the\nbehavior for NOW(), which returns a constant time that\nindicates the\ntime at which the statement began to execute. (Within a\nstored routine\nor trigger, NOW() returns the time at which the routine or\ntriggering\nstatement began to execute.)\n \nIn addition, changing the timestamp system variable with a\nSET timestamp statement affects the value returned by\nNOW() but not by SYSDATE(). This means that timestamp\nsettings in the\nbinary log have no effect on invocations of SYSDATE().\n \nBecause SYSDATE() can return different values even within\nthe same\nstatement, and is not affected by SET TIMESTAMP, it is\nnon-deterministic and therefore unsafe for replication if\nstatement-based binary logging is used. If that is a\nproblem, you can\nuse row-based logging, or start the server with the mysqld\noption --sysdate-is-now to cause SYSDATE() to be an alias\nfor NOW(). The non-deterministic nature of SYSDATE() also\nmeans that indexes cannot be used for evaluating expressions\nthat refer to it, and that statements using the SYSDATE()\nfunction are unsafe for statement-based replication.\n \n\nDifference between NOW() and SYSDATE():\n \nSELECT NOW(), SLEEP(2), NOW();\n+---------------------+----------+---------------------+\n| NOW() | SLEEP(2) | NOW() |\n+---------------------+----------+---------------------+\n| 2010-03-27 13:23:40 | 0 | 2010-03-27 13:23:40 |\n+---------------------+----------+---------------------+\n \nSELECT SYSDATE(), SLEEP(2), SYSDATE();\n+---------------------+----------+---------------------+\n| SYSDATE() | SLEEP(2) | SYSDATE() |\n+---------------------+----------+---------------------+\n| 2010-03-27 13:23:52 | 0 | 2010-03-27 13:23:54 |\n+---------------------+----------+---------------------+\n \nWith precision:\n \nSELECT SYSDATE(4);\n+--------------------------+\n| SYSDATE(4) |\n+--------------------------+\n| 2018-07-10 10:17:13.1689 |\n+--------------------------+ +description=Returns the current date and time as a value in 'YYYY-MM-DD HH:MM:SS' or\nYYYYMMDDHHMMSS.uuuuuu format, depending on whether the function is used in a\nstring or numeric context.\n\nThe optional precision determines the microsecond precision. See Microseconds\nin MariaDB.\n\nSYSDATE() returns the time at which it executes. This differs from the\nbehavior for NOW(), which returns a constant time that indicates the time at\nwhich the statement began to execute. (Within a stored routine or trigger,\nNOW() returns the time at which the routine or triggering statement began to\nexecute.)\n\nIn addition, changing the timestamp system variable with a SET timestamp\nstatement affects the value returned by NOW() but not by SYSDATE(). This means\nthat timestamp settings in the binary log have no effect on invocations of\nSYSDATE().\n\nBecause SYSDATE() can return different values even within the same statement,\nand is not affected by SET TIMESTAMP, it is non-deterministic and therefore\nunsafe for replication if statement-based binary logging is used. If that is a\nproblem, you can use row-based logging, or start the server with the mysqld\noption --sysdate-is-now to cause SYSDATE() to be an alias for NOW(). The\nnon-deterministic nature of SYSDATE() also means that indexes cannot be used\nfor evaluating expressions that refer to it, and that statements using the\nSYSDATE() function are unsafe for statement-based replication.\n\nExamples\n--------\n\nDifference between NOW() and SYSDATE():\n\nSELECT NOW(), SLEEP(2), NOW();\n+---------------------+----------+---------------------+\n| NOW() | SLEEP(2) | NOW() |\n+---------------------+----------+---------------------+\n| 2010-03-27 13:23:40 | 0 | 2010-03-27 13:23:40 |\n+---------------------+----------+---------------------+\n\nSELECT SYSDATE(), SLEEP(2), SYSDATE();\n+---------------------+----------+---------------------+\n| SYSDATE() | SLEEP(2) | SYSDATE() |\n+---------------------+----------+---------------------+\n| 2010-03-27 13:23:52 | 0 | 2010-03-27 13:23:54 |\n+---------------------+----------+---------------------+\n\nWith precision:\n\nSELECT SYSDATE(4);\n+--------------------------+\n ... [SYSTEM_USER] declaration= category=Information Functions -description=SYSTEM_USER() is a synonym for USER(). +description=SYSTEM_USER() is a synonym for USER().\n\nURL: https://mariadb.com/kb/en/system_user/ +[SYS_GUID] +declaration= +category=Miscellaneous Functions +description=Returns a 16-byte globally unique identifier (GUID), similar to the UUID\nfunction, but without the - character.\n\nExample\n-------\n\nSELECT SYS_GUID();\n+----------------------------------+\n| SYS_GUID() |\n+----------------------------------+\n| 2C574E45BA2811EBB265F859713E4BE4 |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/sys_guid/ [TAN] declaration=X category=Numeric Functions -description=Returns the tangent of X, where X is given in radians.\n \n\nSELECT TAN(0.7853981633974483);\n+-------------------------+\n| TAN(0.7853981633974483) |\n+-------------------------+\n| 0.9999999999999999 |\n+-------------------------+\n \nSELECT TAN(PI());\n+-----------------------+\n| TAN(PI()) |\n+-----------------------+\n| -1.22460635382238e-16 |\n+-----------------------+\n \nSELECT TAN(PI()+1);\n+-----------------+\n| TAN(PI()+1) |\n+-----------------+\n| 1.5574077246549 |\n+-----------------+\n \nSELECT TAN(RADIANS(PI()));\n+--------------------+\n| TAN(RADIANS(PI())) |\n+--------------------+\n| 0.0548861508080033 |\n+--------------------+ +description=Returns the tangent of X, where X is given in radians.\n\nExamples\n--------\n\nSELECT TAN(0.7853981633974483);\n+-------------------------+\n| TAN(0.7853981633974483) |\n+-------------------------+\n| 0.9999999999999999 |\n+-------------------------+\n\nSELECT TAN(PI());\n+-----------------------+\n| TAN(PI()) |\n+-----------------------+\n| -1.22460635382238e-16 |\n+-----------------------+\n\nSELECT TAN(PI()+1);\n+-----------------+\n| TAN(PI()+1) |\n+-----------------+\n| 1.5574077246549 |\n+-----------------+\n\nSELECT TAN(RADIANS(PI()));\n+--------------------+\n| TAN(RADIANS(PI())) |\n+--------------------+\n| 0.0548861508080033 |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/tan/ +[TEXT] +declaration=M +category=Data Types +description=A TEXT column with a maximum length of 65,535 (216 - 1) characters. The\neffective maximum length is less if the value contains multi-byte characters.\nEach TEXT value is stored using a two-byte length prefix that indicates the\nnumber of bytes in the value. If you need a bigger storage, consider using\nMEDIUMTEXT instead.\n\nAn optional length M can be given for this type. If this is done, MariaDB\ncreates the column as the smallest TEXT type large enough to hold values M\ncharacters long.\n\nBefore MariaDB 10.2, all MariaDB collations were of type PADSPACE, meaning\nthat TEXT (as well as VARCHAR and CHAR values) are compared without regard for\ntrailing spaces. This does not apply to the LIKE pattern-matching operator,\nwhich takes into account trailing spaces.\n\nBefore MariaDB 10.2.1, BLOB and TEXT columns could not be assigned a DEFAULT\nvalue. This restriction was lifted in MariaDB 10.2.1.\n\nExamples\n--------\n\nTrailing spaces:\n\nCREATE TABLE strtest (d TEXT(10));\nINSERT INTO strtest VALUES('Maria ');\n\nSELECT d='Maria',d='Maria ' FROM strtest;\n+-----------+--------------+\n| d='Maria' | d='Maria ' |\n+-----------+--------------+\n| 1 | 1 |\n+-----------+--------------+\n\nSELECT d LIKE 'Maria',d LIKE 'Maria ' FROM strtest;\n+----------------+-------------------+\n| d LIKE 'Maria' | d LIKE 'Maria ' |\n+----------------+-------------------+\n| 0 | 1 |\n+----------------+-------------------+\n\nIndexing\n--------\n\nTEXT columns can only be indexed over a specified length. This means that they\ncannot be used as the primary key of a table norm until MariaDB 10.4, can a\nunique index be created on them.\n\nMariaDB starting with 10.4\n--------------------------\nStarting with MariaDB 10.4, a unique index can be created on a TEXT column.\n ... +[TIME] +declaration= +category=Data Types +description=A time. The range is '-838:59:59.999999' to '838:59:59.999999'. Microsecond\nprecision can be from 0-6; if not specified 0 is used. Microseconds have been\navailable since MariaDB 5.3.\n\nMariaDB displays TIME values in 'HH:MM:SS.ssssss' format, but allows\nassignment of times in looser formats, including 'D HH:MM:SS', 'HH:MM:SS',\n'HH:MM', 'D HH:MM', 'D HH', 'SS', or 'HHMMSS', as well as permitting dropping\nof any leading zeros when a delimiter is provided, for example '3:9:10'. For\ndetails, see date and time literals.\n\nMariaDB 10.1.2 introduced the --mysql56-temporal-format option, on by default,\nwhich allows MariaDB to store TIMEs using the same low-level format MySQL 5.6\nuses.\n\nInternal Format\n---------------\n\nIn MariaDB 10.1.2 a new temporal format was introduced from MySQL 5.6 that\nalters how the TIME, DATETIME and TIMESTAMP columns operate at lower levels.\nThese changes allow these temporal data types to have fractional parts and\nnegative values. You can disable this feature using the\nmysql56_temporal_format system variable.\n\nTables that include TIMESTAMP values that were created on an older version of\nMariaDB or that were created while the mysql56_temporal_format system variable\nwas disabled continue to store data using the older data type format.\n\nIn order to update table columns from the older format to the newer format,\nexecute an ALTER TABLE... MODIFY COLUMN statement that changes the column to\nthe *same* data type. This change may be needed if you want to export the\ntable's tablespace and import it onto a server that has\nmysql56_temporal_format=ON set (see MDEV-15225).\n\nFor instance, if you have a TIME column in your table:\n\nSHOW VARIABLES LIKE 'mysql56_temporal_format';\n\n+-------------------------+-------+\n| Variable_name | Value |\n+-------------------------+-------+\n| mysql56_temporal_format | ON |\n+-------------------------+-------+\n\nALTER TABLE example_table MODIFY ts_col TIME;\n\nWhen MariaDB executes the ALTER TABLE statement, it converts the data from the\nolder temporal format to the newer one.\n\nIn the event that you have several tables and columns using temporal data\ntypes that you want to switch over to the new format, make sure the system\n ... [TIMEDIFF] declaration=expr1,expr2 category=Date and Time Functions -description=TIMEDIFF() returns expr1 - expr2 expressed as a time value.\nexpr1 and\nexpr2 are time or date-and-time expressions, but both must\nbe of the\nsame type.\n \n\nSELECT TIMEDIFF(''2000:01:01 00:00:00'', ''2000:01:01\n00:00:00.000001'');\n+---------------------------------------------------------------+\n| TIMEDIFF(''2000:01:01 00:00:00'', ''2000:01:01\n00:00:00.000001'') |\n+---------------------------------------------------------------+\n| -00:00:00.000001 |\n+---------------------------------------------------------------+\n \nSELECT TIMEDIFF(''2008-12-31 23:59:59.000001'', ''2008-12-30\n01:01:01.000002'');\n+----------------------------------------------------------------------+\n| TIMEDIFF(''2008-12-31 23:59:59.000001'', ''2008-12-30\n01:01:01.000002'') |\n+----------------------------------------------------------------------+\n| 46:58:57.999999 |\n+----------------------------------------------------------------------+ +description=TIMEDIFF() returns expr1 - expr2 expressed as a time value. expr1 and expr2\nare time or date-and-time expressions, but both must be of the same type.\n\nExamples\n--------\n\nSELECT TIMEDIFF('2000:01:01 00:00:00', '2000:01:01 00:00:00.000001');\n+---------------------------------------------------------------+\n| TIMEDIFF('2000:01:01 00:00:00', '2000:01:01 00:00:00.000001') |\n+---------------------------------------------------------------+\n| -00:00:00.000001 |\n+---------------------------------------------------------------+\n\nSELECT TIMEDIFF('2008-12-31 23:59:59.000001', '2008-12-30 01:01:01.000002');\n+----------------------------------------------------------------------+\n| TIMEDIFF('2008-12-31 23:59:59.000001', '2008-12-30 01:01:01.000002') |\n+----------------------------------------------------------------------+\n| 46:58:57.999999 |\n+----------------------------------------------------------------------+\n\nURL: https://mariadb.com/kb/en/timediff/ +[TIMESTAMP] +declaration==3;\n+------+\n| i |\n+------+\n| 1 |\n| 2 |\n| 3 |\n| 4 |\n| 5 |\n| 6 |\n+------+\n\nSELECT i FROM seqs WHERE i <= 3 UNION ALL SELECT i FROM seqs WHERE i>=3;\n+------+\n ... +[UNIX_TIMESTAMP] declaration= category=Date and Time Functions -description=If called with no argument, returns a Unix timestamp\n(seconds since\n''1970-01-01 00:00:00'' UTC) as an unsigned integer. If\nUNIX_TIMESTAMP()\nis called with a date argument, it returns the value of the\nargument as seconds\nsince ''1970-01-01 00:00:00'' UTC. date may be a DATE\nstring, a\nDATETIME string, a TIMESTAMP, or a number in\nthe format YYMMDD or YYYYMMDD. The server interprets date as\na value in the\ncurrent time zone and converts it to an internal value in\nUTC. Clients can set\ntheir time zone as described in time zones.\n \nThe inverse function of UNIX_TIMESTAMP() is FROM_UNIXTIME()\n \nUNIX_TIMESTAMP() supports microseconds.\n \nTimestamps in MariaDB have a maximum value of 2147483647,\nequivalent to 2038-01-19 05:14:07. This is due to the\nunderlying 32-bit limitation. Using the function on a date\nbeyond this will result in NULL being returned. Use DATETIME\nas a storage type if you require dates beyond this.\n \nError Handling\n \nReturns NULL for wrong arguments to UNIX_TIMESTAMP(). In\nMySQL and MariaDB before 5.3 wrong arguments to\nUNIX_TIMESTAMP() returned 0. \n \nCompatibility\n \nAs you can see in the examples above,\nUNIX_TIMESTAMP(constant-date-string) returns a timestamp\nwith 6 decimals while MariaDB 5.2 and before returns it\nwithout decimals. This can cause a problem if you are using\nUNIX_TIMESTAMP() as a partitioning function. You can fix\nthis by using FLOOR(UNIX_TIMESTAMP(..)) or changing the date\nstring to a date number, like 20080101000000. \n \n\nSELECT UNIX_TIMESTAMP();\n+------------------+\n| UNIX_TIMESTAMP() |\n+------------------+\n| 1269711082 |\n+------------------+\n \nSELECT UNIX_TIMESTAMP(''2007-11-30 10:30:19'');\n+---------------------------------------+\n| UNIX_TIMESTAMP(''2007-11-30 10:30:19'') |\n+---------------------------------------+\n| 1196436619.000000 |\n+---------------------------------------+\n \nSELECT UNIX_TIMESTAMP("2007-11-30 10:30:19.123456");\n+----------------------------------------------+\n| unix_timestamp("2007-11-30 10:30:19.123456") |\n+----------------------------------------------+\n| 1196411419.123456 |\n+----------------------------------------------+\n \nSELECT FROM_UNIXTIME(UNIX_TIMESTAMP(''2007-11-30\n10:30:19''));\n+------------------------------------------------------+\n| FROM_UNIXTIME(UNIX_TIMESTAMP(''2007-11-30 10:30:19'')) |\n+------------------------------------------------------+\n| 2007-11-30 10:30:19.000000 |\n+------------------------------------------------------+\n \nSELECT FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(''2007-11-30\n10:30:19'')));\n+-------------------------------------------------------------+\n| FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(''2007-11-30\n10:30:19''))) |\n+-------------------------------------------------------------+\n| 2007-11-30 10:30:19 |\n+-------------------------------------------------------------+ -[UNIX_TIMESTAMP2] -name=UNIX_TIMESTAMP -declaration=date -category=Date and Time Functions -description=If called with no argument, returns a Unix timestamp\n(seconds since\n''1970-01-01 00:00:00'' UTC) as an unsigned integer. If\nUNIX_TIMESTAMP()\nis called with a date argument, it returns the value of the\nargument as seconds\nsince ''1970-01-01 00:00:00'' UTC. date may be a DATE\nstring, a\nDATETIME string, a TIMESTAMP, or a number in\nthe format YYMMDD or YYYYMMDD. The server interprets date as\na value in the\ncurrent time zone and converts it to an internal value in\nUTC. Clients can set\ntheir time zone as described in time zones.\n \nThe inverse function of UNIX_TIMESTAMP() is FROM_UNIXTIME()\n \nUNIX_TIMESTAMP() supports microseconds.\n \nTimestamps in MariaDB have a maximum value of 2147483647,\nequivalent to 2038-01-19 05:14:07. This is due to the\nunderlying 32-bit limitation. Using the function on a date\nbeyond this will result in NULL being returned. Use DATETIME\nas a storage type if you require dates beyond this.\n \nError Handling\n \nReturns NULL for wrong arguments to UNIX_TIMESTAMP(). In\nMySQL and MariaDB before 5.3 wrong arguments to\nUNIX_TIMESTAMP() returned 0. \n \nCompatibility\n \nAs you can see in the examples above,\nUNIX_TIMESTAMP(constant-date-string) returns a timestamp\nwith 6 decimals while MariaDB 5.2 and before returns it\nwithout decimals. This can cause a problem if you are using\nUNIX_TIMESTAMP() as a partitioning function. You can fix\nthis by using FLOOR(UNIX_TIMESTAMP(..)) or changing the date\nstring to a date number, like 20080101000000. \n \n\nSELECT UNIX_TIMESTAMP();\n+------------------+\n| UNIX_TIMESTAMP() |\n+------------------+\n| 1269711082 |\n+------------------+\n \nSELECT UNIX_TIMESTAMP(''2007-11-30 10:30:19'');\n+---------------------------------------+\n| UNIX_TIMESTAMP(''2007-11-30 10:30:19'') |\n+---------------------------------------+\n| 1196436619.000000 |\n+---------------------------------------+\n \nSELECT UNIX_TIMESTAMP("2007-11-30 10:30:19.123456");\n+----------------------------------------------+\n| unix_timestamp("2007-11-30 10:30:19.123456") |\n+----------------------------------------------+\n| 1196411419.123456 |\n+----------------------------------------------+\n \nSELECT FROM_UNIXTIME(UNIX_TIMESTAMP(''2007-11-30\n10:30:19''));\n+------------------------------------------------------+\n| FROM_UNIXTIME(UNIX_TIMESTAMP(''2007-11-30 10:30:19'')) |\n+------------------------------------------------------+\n| 2007-11-30 10:30:19.000000 |\n+------------------------------------------------------+\n \nSELECT FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(''2007-11-30\n10:30:19'')));\n+-------------------------------------------------------------+\n| FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(''2007-11-30\n10:30:19''))) |\n+-------------------------------------------------------------+\n| 2007-11-30 10:30:19 |\n+-------------------------------------------------------------+ +description=If called with no argument, returns a Unix timestamp (seconds since\n'1970-01-01 00:00:00' UTC) as an unsigned integer. If UNIX_TIMESTAMP() is\ncalled with a date argument, it returns the value of the argument as seconds\nsince '1970-01-01 00:00:00' UTC. date may be a DATE string, a DATETIME string,\na TIMESTAMP, or a number in the format YYMMDD or YYYYMMDD. The server\ninterprets date as a value in the current time zone and converts it to an\ninternal value in UTC. Clients can set their time zone as described in time\nzones.\n\nThe inverse function of UNIX_TIMESTAMP() is FROM_UNIXTIME()\n\nUNIX_TIMESTAMP() supports microseconds.\n\nTimestamps in MariaDB have a maximum value of 2147483647, equivalent to\n2038-01-19 05:14:07. This is due to the underlying 32-bit limitation. Using\nthe function on a date beyond this will result in NULL being returned. Use\nDATETIME as a storage type if you require dates beyond this.\n\nError Handling\n--------------\n\nReturns NULL for wrong arguments to UNIX_TIMESTAMP(). In MySQL and MariaDB\nbefore 5.3 wrong arguments to UNIX_TIMESTAMP() returned 0.\n\nCompatibility\n-------------\n\nAs you can see in the examples above, UNIX_TIMESTAMP(constant-date-string)\nreturns a timestamp with 6 decimals while MariaDB 5.2 and before returns it\nwithout decimals. This can cause a problem if you are using UNIX_TIMESTAMP()\nas a partitioning function. You can fix this by using\nFLOOR(UNIX_TIMESTAMP(..)) or changing the date string to a date number, like\n20080101000000.\n\nExamples\n--------\n\nSELECT UNIX_TIMESTAMP();\n+------------------+\n| UNIX_TIMESTAMP() |\n+------------------+\n| 1269711082 |\n+------------------+\n\nSELECT UNIX_TIMESTAMP('2007-11-30 10:30:19');\n+---------------------------------------+\n| UNIX_TIMESTAMP('2007-11-30 10:30:19') |\n+---------------------------------------+\n| 1196436619.000000 |\n+---------------------------------------+\n ... [UPDATEXML] declaration=xml_target, xpath_expr, new_xml category=String Functions -description=This function replaces a single portion of a given fragment\nof XML markup\nxml_target with a new XML fragment new_xml, and then returns\nthe\nchanged XML. The portion of xml_target that is replaced\nmatches an XPath\nexpression xpath_expr supplied by the user. If no expression\nmatching\nxpath_expr is found, or if multiple matches are found, the\nfunction returns\nthe original xml_target XML fragment. All three arguments\nshould be\nstrings.\n \n\nSELECT\n UpdateXML(''ccc'', ''/a'', ''fff'') AS val1,\n UpdateXML(''ccc'', ''/b'', ''fff'') AS val2,\n UpdateXML(''ccc'', ''//b'', ''fff'') AS val3,\n UpdateXML(''ccc'', ''/a/d'', ''fff'') AS val4,\n UpdateXML(''ccc'', ''/a/d'', ''fff'') AS val5\n \G\n*************************** 1. row\n***************************\nval1: fff\nval2: ccc\nval3: fff\nval4: cccfff\nval5: ccc\n1 row in set (0.00 sec) +description=This function replaces a single portion of a given fragment of XML markup\nxml_target with a new XML fragment new_xml, and then returns the changed XML.\nThe portion of xml_target that is replaced matches an XPath expression\nxpath_expr supplied by the user. If no expression matching xpath_expr is\nfound, or if multiple matches are found, the function returns the original\nxml_target XML fragment. All three arguments should be strings.\n\nExamples\n--------\n\nSELECT\n UpdateXML('ccc', '/a', 'fff') AS val1,\n UpdateXML('ccc', '/b', 'fff') AS val2,\n UpdateXML('ccc', '//b', 'fff') AS val3,\n UpdateXML('ccc', '/a/d', 'fff') AS val4,\n UpdateXML('ccc', '/a/d', 'fff') AS val5\n \G\n*************************** 1. row ***************************\nval1: fff\nval2: ccc\nval3: fff\nval4: cccfff\nval5: ccc\n1 row in set (0.00 sec)\n\nURL: https://mariadb.com/kb/en/updatexml/ [UPPER] declaration=str category=String Functions -description=Returns the string str with all characters changed to\nuppercase\naccording to the current character set mapping. The default\nis latin1\n(cp1252 West European).\n \nSELECT UPPER(surname), givenname FROM users ORDER BY\nsurname;\n \n+----------------+------------+\n| UPPER(surname) | givenname |\n+----------------+------------+\n| ABEL | Jacinto |\n| CASTRO | Robert |\n| COSTA | Phestos |\n| MOSCHELLA | Hippolytos |\n+----------------+------------+\n \nUPPER() is ineffective when applied to binary strings\n(BINARY,\nVARBINARY, BLOB). The description of \nLOWER() shows how to\nperform lettercase conversion of binary strings. +description=Returns the string str with all characters changed to uppercase according to\nthe current character set mapping. The default is latin1 (cp1252 West\nEuropean).\n\nUCASE is a synonym.\n\nSELECT UPPER(surname), givenname FROM users ORDER BY surname;\n+----------------+------------+\n| UPPER(surname) | givenname |\n+----------------+------------+\n| ABEL | Jacinto |\n| CASTRO | Robert |\n| COSTA | Phestos |\n| MOSCHELLA | Hippolytos |\n+----------------+------------+\n\nUPPER() is ineffective when applied to binary strings (BINARY, VARBINARY,\nBLOB). The description of LOWER() shows how to perform lettercase conversion\nof binary strings.\n\nPrior to MariaDB 11.3, the query optimizer did not handle queries of the\nformat UCASE(varchar_col)=.... An optimizer_switch option,\nsargable_casefold=ON, was added in MariaDB 11.3.0 to handle this case.\n(MDEV-31496)\n\nURL: https://mariadb.com/kb/en/upper/ [USER] declaration= category=Information Functions -description=Returns the current MariaDB user name and host name, given\nwhen authenticating to MariaDB, as a string in the utf8\ncharacter set.\n \nNote that the value of USER() may differ from the value of\nCURRENT_USER(), which is the user used to authenticate the\ncurrent client. \nCURRENT_ROLE() returns the current active role.\n \nSYSTEM_USER() and SESSION_USER are synonyms for USER().\n \nStatements using the USER() function or one of its synonyms\nare not safe for statement level replication.\n \n\nshell> mysql --user="anonymous"\n \nMariaDB [(none)]> select user(),current_user();\n+---------------------+----------------+\n| user() | current_user() |\n+---------------------+----------------+\n| anonymous@localhost | @localhost |\n+---------------------+----------------+ +description=Returns the current MariaDB user name and host name, given when authenticating\nto MariaDB, as a string in the utf8 character set.\n\nNote that the value of USER() may differ from the value of CURRENT_USER(),\nwhich is the user used to authenticate the current client. CURRENT_ROLE()\nreturns the current active role.\n\nSYSTEM_USER() and SESSION_USER are synonyms for USER().\n\nStatements using the USER() function or one of its synonyms are not safe for\nstatement level replication.\n\nExamples\n--------\n\nshell> mysql --user="anonymous"\n\nSELECT USER(),CURRENT_USER();\n+---------------------+----------------+\n| USER() | CURRENT_USER() |\n+---------------------+----------------+\n| anonymous@localhost | @localhost |\n+---------------------+----------------+\n\nTo select only the IP address, use SUBSTRING_INDEX(),\n\nSELECT SUBSTRING_INDEX(USER(), '@', -1);\n+----------------------------------+\n| SUBSTRING_INDEX(USER(), '@', -1) |\n+----------------------------------+\n| 192.168.0.101 |\n+----------------------------------+\n\nURL: https://mariadb.com/kb/en/user/ +[UTC_DATE] +declaration= +category=Date and Time Functions +description=Returns the current UTC date as a value in 'YYYY-MM-DD' or YYYYMMDD format,\ndepending on whether the function is used in a string or numeric context.\n\nExamples\n--------\n\nSELECT UTC_DATE(), UTC_DATE() + 0;\n+------------+----------------+\n| UTC_DATE() | UTC_DATE() + 0 |\n+------------+----------------+\n| 2010-03-27 | 20100327 |\n+------------+----------------+\n\nURL: https://mariadb.com/kb/en/utc_date/ +[UTC_TIME] +declaration=[precision] +category=Date and Time Functions +description=Returns the current UTC time as a value in 'HH:MM:SS' or HHMMSS.uuuuuu format,\ndepending on whether the function is used in a string or numeric context.\n\nThe optional precision determines the microsecond precision. See Microseconds\nin MariaDB.\n\nExamples\n--------\n\nSELECT UTC_TIME(), UTC_TIME() + 0;\n+------------+----------------+\n| UTC_TIME() | UTC_TIME() + 0 |\n+------------+----------------+\n| 17:32:34 | 173234.000000 |\n+------------+----------------+\n\nWith precision:\n\nSELECT UTC_TIME(5);\n+----------------+\n| UTC_TIME(5) |\n+----------------+\n| 07:52:50.78369 |\n+----------------+\n\nURL: https://mariadb.com/kb/en/utc_time/ +[UTC_TIMESTAMP] +declaration=[precision] +category=Date and Time Functions +description=Returns the current UTC date and time as a value in 'YYYY-MM-DD HH:MM:SS' or\nYYYYMMDDHHMMSS.uuuuuu format, depending on whether the function is used in a\nstring or numeric context.\n\nThe optional precision determines the microsecond precision. See Microseconds\nin MariaDB.\n\nExamples\n--------\n\nSELECT UTC_TIMESTAMP(), UTC_TIMESTAMP() + 0;\n+---------------------+-----------------------+\n| UTC_TIMESTAMP() | UTC_TIMESTAMP() + 0 |\n+---------------------+-----------------------+\n| 2010-03-27 17:33:16 | 20100327173316.000000 |\n+---------------------+-----------------------+\n\nWith precision:\n\nSELECT UTC_TIMESTAMP(4);\n+--------------------------+\n| UTC_TIMESTAMP(4) |\n+--------------------------+\n| 2018-07-10 07:51:09.1019 |\n+--------------------------+\n\nURL: https://mariadb.com/kb/en/utc_timestamp/ [UUID] declaration= category=Miscellaneous Functions -description=Returns a Universal Unique Identifier (UUID) generated\naccording to "DCE 1.1:\nRemote Procedure Call" (Appendix A) CAE (Common\nApplications Environment)\nSpecifications published by The Open Group in October\n1997 \n(Document Number C706).\n \nA UUID is designed as a number that is globally unique in\nspace and time. Two\ncalls to UUID() are expected to generate two different\nvalues, even if these calls are performed on two separate\ncomputers that are\nnot connected to each other.\n \nA UUID is a 128-bit number represented by a utf8 string of\nfive\nhexadecimal numbers in aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\nformat:\nThe first three numbers are generated from a timestamp.\nThe fourth number preserves temporal uniqueness in case the\ntimestamp value\n loses monotonicity (for example, due to daylight saving\ntime).\nThe fifth number is an IEEE 802 node number that provides\nspatial uniqueness.\n A random number is substituted if the latter is not\navailable (for example,\n because the host computer has no Ethernet card, or we do\nnot know how to find\n the hardware address of an interface on your operating\nsystem). In this case,\n spatial uniqueness cannot be guaranteed. Nevertheless, a\ncollision should\n have very low probability.\n \nCurrently, the MAC address of an interface is taken into\naccount only on FreeBSD and Linux. On other operating\nsystems, MariaDB uses a randomly generated 48-bit number.\n \nStatements using the UUID() function are not safe for\nreplication.\n \nUUID() results are intended to be unique, but cannot always\nbe relied upon to unpredictable and unguessable, so should\nnot be relied upon for these purposes.\n \n\nSELECT UUID();\n+--------------------------------------+\n| UUID() |\n+--------------------------------------+\n| cd41294a-afb0-11df-bc9b-00241dd75637 |\n+--------------------------------------+ +description=Returns a Universally Unique Identifier (UUID).\n\nA UUID is designed as a number that is globally unique in space and time. Two\ncalls to UUID() are expected to generate two different values, even if these\ncalls are performed on two separate computers that are not connected to each\nother.\n\nUUID() results are intended to be unique, but cannot always be relied upon to\nbe unpredictable and unguessable.\n\nA UUID is a 128-bit number represented by a utf8 string of five hexadecimal\nnumbers in aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee format:\n\n* The first three numbers are generated from a timestamp.\n* The fourth number preserves temporal uniqueness in case the timestamp value\n loses monotonicity (for example, due to daylight saving time).\n* The fifth number is an IEEE 802 node number that provides spatial uniqueness.\n A random number is substituted if the latter is not available (for example,\n because the host computer has no Ethernet card, or we do not know how to find\n the hardware address of an interface on your operating system). In this case,\n spatial uniqueness cannot be guaranteed. Nevertheless, a collision should\n have very low probability.\n\nCurrently, the MAC address of an interface is taken into account only on\nFreeBSD and Linux. On other operating systems, MariaDB uses a randomly\ngenerated 48-bit number.\n\nStatements using the UUID() function are not safe for statement-based\nreplication.\n\nThe function generates a UUIDv1 and the results are generated according to the\n"DCE 1.1:Remote Procedure Call" (Appendix A) CAE (Common Applications\nEnvironment) Specifications published by The Open Group in October 1997\n(Document Number C706).\n\nExamples\n--------\n\nSELECT UUID();\n+--------------------------------------+\n| UUID() |\n+--------------------------------------+\n| cd41294a-afb0-11df-bc9b-00241dd75637 |\n+--------------------------------------+\n\nURL: https://mariadb.com/kb/en/uuid/ [UUID_SHORT] declaration= category=Miscellaneous Functions -description=Returns a "short" universal identifier as a 64-bit\nunsigned integer (rather\nthan a string-form 128-bit identifier as returned by the\nUUID() function).\n \nThe value of UUID_SHORT() is guaranteed to be unique if the\nfollowing conditions hold:\nThe server_id of the current host is unique among your set\nof master and\n slave servers\nserver_id is between 0 and 255\nYou don''t set back your system time for your server between\nmysqld restarts\nYou do not invoke UUID_SHORT() on average more than 16\n million times per second between mysqld restarts\n \nThe UUID_SHORT() return value is constructed this way:\n \n (server_id & 255) +description=Returns a "short" universally unique identifier as a 64-bit unsigned integer\n(rather than a string-form 128-bit identifier as returned by the UUID()\nfunction).\n\nThe value of UUID_SHORT() is guaranteed to be unique if the following\nconditions hold:\n\n* The server_id of the current host is unique among your set of master and\n slave servers\n* server_id is between 0 and 255\n* You don't set back your system time for your server between mysqld restarts\n* You do not invoke UUID_SHORT() on average more than 16\n million times per second between mysqld restarts\n\nThe UUID_SHORT() return value is constructed this way:\n\n(server_id & 255) << 56\n+ (server_startup_time_in_seconds << 24)\n+ incremented_variable++;\n\nStatements using the UUID_SHORT() function are not safe for statement-based\nreplication.\n\nExamples\n--------\n\nSELECT UUID_SHORT();\n+-------------------+\n| UUID_SHORT() |\n+-------------------+\n| 21517162376069120 |\n+-------------------+\n\ncreate table t1 (a bigint unsigned default(uuid_short()) primary key);\ninsert into t1 values(),();\nselect * from t1;\n+-------------------+\n| a |\n+-------------------+\n| 98113699159474176 |\n| 98113699159474177 |\n+-------------------+\n\nURL: https://mariadb.com/kb/en/uuid_short/ [VARBINARY] declaration=M category=Data Types -description=The VARBINARY type is similar to the VARCHAR type, but\nstores binary byte strings rather than non-binary character\nstrings. M represents the maximum column length in bytes. \n \nIt contains no character set, and comparison and sorting are\nbased on the numeric value of the bytes.\n \nIf the maximum length is exceeded, and SQL strict mode is\nnot enabled , the extra characters will be dropped with a\nwarning. If strict mode is enabled, an error will occur.\n \nUnlike BINARY values, VARBINARYs are not right-padded when\ninserting.\n \nOracle Mode\n \nIn Oracle mode from MariaDB 10.3, RAW is a synonym for\nVARBINARY.\n \n\nInserting too many characters, first with strict mode off,\nthen with it on:\n \nCREATE TABLE varbins (a VARBINARY(10));\n \nINSERT INTO varbins VALUES(''12345678901'');\nQuery OK, 1 row affected, 1 warning (0.04 sec)\n \nSELECT * FROM varbins;\n \n+------------+\n| a |\n+------------+\n| 1234567890 |\n+------------+\n \nSET sql_mode=''STRICT_ALL_TABLES'';\n \nINSERT INTO varbins VALUES(''12345678901'');\nERROR 1406 (22001): Data too long for column ''a'' at row 1\n \nSorting is performed with the byte value:\n \nTRUNCATE varbins;\n \nINSERT INTO varbins VALUES(''A''),(''B''),(''a''),(''b'');\n \nSELECT * FROM varbins ORDER BY a;\n \n+------+\n| a |\n+------+\n| A |\n| B |\n| a |\n| b |\n+------+\n \nUsing CAST to sort as a CHAR instead:\n \nSELECT * FROM varbins ORDER BY CAST(a AS CHAR);\n+------+\n| a |\n+------+\n| a |\n| A |\n| b |\n| B |\n+------+ +description=The VARBINARY type is similar to the VARCHAR type, but stores binary byte\nstrings rather than non-binary character strings. M represents the maximum\ncolumn length in bytes.\n\nIt contains no character set, and comparison and sorting are based on the\nnumeric value of the bytes.\n\nIf the maximum length is exceeded, and SQL strict mode is not enabled , the\nextra characters will be dropped with a warning. If strict mode is enabled, an\nerror will occur.\n\nUnlike BINARY values, VARBINARYs are not right-padded when inserting.\n\nOracle Mode\n-----------\n\nIn Oracle mode from MariaDB 10.3, RAW is a synonym for VARBINARY.\n\nExamples\n--------\n\nInserting too many characters, first with strict mode off, then with it on:\n\nCREATE TABLE varbins (a VARBINARY(10));\n\nINSERT INTO varbins VALUES('12345678901');\nQuery OK, 1 row affected, 1 warning (0.04 sec)\n\nSELECT * FROM varbins;\n+------------+\n| a |\n+------------+\n| 1234567890 |\n+------------+\n\nSET sql_mode='STRICT_ALL_TABLES';\n\nINSERT INTO varbins VALUES('12345678901');\nERROR 1406 (22001): Data too long for column 'a' at row 1\n\nSorting is performed with the byte value:\n\nTRUNCATE varbins;\n\nINSERT INTO varbins VALUES('A'),('B'),('a'),('b');\n\nSELECT * FROM varbins ORDER BY a;\n+------+\n| a |\n+------+\n ... +[VARCHAR] +declaration=M +category=Data Types +description=A variable-length string. M represents the maximum column length in\ncharacters. The range of M is 0 to 65,532. The effective maximum length of a\nVARCHAR is subject to the maximum row size and the character set used. For\nexample, utf8 characters can require up to three bytes per character, so a\nVARCHAR column that uses the utf8 character set can be declared to be a\nmaximum of 21,844 characters.\n\nNote: For the ColumnStore engine, M represents the maximum column length in\nbytes.\n\nMariaDB stores VARCHAR values as a one-byte or two-byte length prefix plus\ndata. The length prefix indicates the number of bytes in the value. A VARCHAR\ncolumn uses one length byte if values require no more than 255 bytes, two\nlength bytes if values may require more than 255 bytes.\n\nMariaDB follows the standard SQL specification, and does not remove trailing\nspaces from VARCHAR values.\n\nVARCHAR(0) columns can contain 2 values: an empty string or NULL. Such columns\ncannot be part of an index. The CONNECT storage engine does not support\nVARCHAR(0).\n\nVARCHAR is shorthand for CHARACTER VARYING. NATIONAL VARCHAR is the standard\nSQL way to define that a VARCHAR column should use some predefined character\nset. MariaDB uses utf8 as this predefined character set, as does MySQL 4.1 and\nup. NVARCHAR is shorthand for NATIONAL VARCHAR.\n\nBefore MariaDB 10.2, all MariaDB collations were of type PADSPACE, meaning\nthat VARCHAR (as well as CHAR and TEXT values) are compared without regard for\ntrailing spaces. This does not apply to the LIKE pattern-matching operator,\nwhich takes into account trailing spaces. From MariaDB 10.2, a number of NO\nPAD collations are available.\n\nIf a unique index consists of a column where trailing pad characters are\nstripped or ignored, inserts into that column where values differ only by the\nnumber of trailing pad characters will result in a duplicate-key error.\n\nExamples\n--------\n\nThe following are equivalent:\n\nVARCHAR(30) CHARACTER SET utf8\nNATIONAL VARCHAR(30)\nNVARCHAR(30)\nNCHAR VARCHAR(30)\nNATIONAL CHARACTER VARYING(30)\nNATIONAL CHAR VARYING(30)\n\nTrailing spaces:\n ... [VARIANCE] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the population standard variance of expr. This is an\nextension to\nstandard SQL. The standard SQL function VAR_POP() can be\nused\ninstead.\n \nVariance is calculated by\nworking out the mean for the set\nfor each number, subtracting the mean and squaring the\nresult\ncalculate the average of the resulting differences\n \nIt is an aggregate function, and so can be used with the\nGROUP BY clause.\n \nFrom MariaDB 10.2.2, VARIANCE() can be used as a window\nfunction.\n \nVARIANCE() returns NULL if there were no matching rows.\n \n\nCREATE TABLE v(i tinyint);\n \nINSERT INTO v VALUES(101),(99);\n \nSELECT VARIANCE(i) FROM v;\n \n+-------------+\n| VARIANCE(i) |\n+-------------+\n| 1.0000 |\n+-------------+\n \nINSERT INTO v VALUES(120),(80);\n \nSELECT VARIANCE(i) FROM v;\n \n+-------------+\n| VARIANCE(i) |\n+-------------+\n| 200.5000 |\n+-------------+\n \nAs an aggregate function:\n \nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n \nINSERT INTO stats VALUES \n (''a'',1),(''a'',2),(''a'',3),\n (''b'',11),(''b'',12),(''b'',20),(''b'',30),(''b'',60);\n \nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n \n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n \nAs a window function:\n \nCREATE OR REPLACE TABLE student_test (name CHAR(10), test\nCHAR(10), score TINYINT);\n \nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87);\n \nSELECT name, test, score, VAR_POP(score) \n OVER (PARTITION BY test) AS variance_results FROM\nstudent_test;\n \n+---------+--------+-------+------------------+\n| name | test | score | variance_results |\n+---------+--------+-------+------------------+\n| Chun | SQL | 75 | 287.1875 |\n| Chun | Tuning | 73 | 582.0000 |\n| Esben | SQL | 43 | 287.1875 |\n| Esben | Tuning | 31 | 582.0000 |\n| Kaolin | SQL | 56 | 287.1875 |\n| Kaolin | Tuning | 88 | 582.0000 |\n| Tatiana | SQL | 87 | 287.1875 |\n+---------+--------+-------+------------------+ +description=Returns the population standard variance of expr. This is an extension to\nstandard SQL. The standard SQL function VAR_POP() can be used instead.\n\nVariance is calculated by\n\n* working out the mean for the set\n* for each number, subtracting the mean and squaring the result\n* calculate the average of the resulting differences\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nVARIANCE() can be used as a window function.\n\nVARIANCE() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nCREATE TABLE v(i tinyint);\n\nINSERT INTO v VALUES(101),(99);\n\nSELECT VARIANCE(i) FROM v;\n+-------------+\n| VARIANCE(i) |\n+-------------+\n| 1.0000 |\n+-------------+\n\nINSERT INTO v VALUES(120),(80);\n\nSELECT VARIANCE(i) FROM v;\n+-------------+\n| VARIANCE(i) |\n+-------------+\n| 200.5000 |\n+-------------+\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n ... [VAR_POP] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the population standard variance of expr. It\nconsiders rows as\nthe whole population, not as a sample, so it has the number\nof rows as\nthe denominator. You can also use VARIANCE(), which is\nequivalent but\nis not standard SQL.\n \nVariance is calculated by\nworking out the mean for the set\nfor each number, subtracting the mean and squaring the\nresult\ncalculate the average of the resulting differences\n \nIt is an aggregate function, and so can be used with the\nGROUP BY clause.\n \nFrom MariaDB 10.2.2, VAR_POP() can be used as a window\nfunction.\n \nVAR_POP() returns NULL if there were no matching rows.\n \n\nCREATE TABLE v(i tinyint);\n \nINSERT INTO v VALUES(101),(99);\n \nSELECT VAR_POP(i) FROM v;\n \n+------------+\n| VAR_POP(i) |\n+------------+\n| 1.0000 |\n+------------+\n \nINSERT INTO v VALUES(120),(80);\n \nSELECT VAR_POP(i) FROM v;\n \n+------------+\n| VAR_POP(i) |\n+------------+\n| 200.5000 |\n+------------+\n \nAs an aggregate function:\n \nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n \nINSERT INTO stats VALUES \n (''a'',1),(''a'',2),(''a'',3),\n (''b'',11),(''b'',12),(''b'',20),(''b'',30),(''b'',60);\n \nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n \n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n \nAs a window function:\n \nCREATE OR REPLACE TABLE student_test (name CHAR(10), test\nCHAR(10), score TINYINT);\n \nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87);\n \nSELECT name, test, score, VAR_POP(score) \n OVER (PARTITION BY test) AS variance_results FROM\nstudent_test;\n \n+---------+--------+-------+------------------+\n| name | test | score | variance_results |\n+---------+--------+-------+------------------+\n| Chun | SQL | 75 | 287.1875 |\n| Chun | Tuning | 73 | 582.0000 |\n| Esben | SQL | 43 | 287.1875 |\n| Esben | Tuning | 31 | 582.0000 |\n| Kaolin | SQL | 56 | 287.1875 |\n| Kaolin | Tuning | 88 | 582.0000 |\n| Tatiana | SQL | 87 | 287.1875 |\n+---------+--------+-------+------------------+ +description=Returns the population standard variance of expr. It considers rows as the\nwhole population, not as a sample, so it has the number of rows as the\ndenominator. You can also use VARIANCE(), which is equivalent but is not\nstandard SQL.\n\nVariance is calculated by\n\n* working out the mean for the set\n* for each number, subtracting the mean and squaring the result\n* calculate the average of the resulting differences\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nVAR_POP() can be used as a window function.\n\nVAR_POP() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nCREATE TABLE v(i tinyint);\n\nINSERT INTO v VALUES(101),(99);\n\nSELECT VAR_POP(i) FROM v;\n+------------+\n| VAR_POP(i) |\n+------------+\n| 1.0000 |\n+------------+\n\nINSERT INTO v VALUES(120),(80);\n\nSELECT VAR_POP(i) FROM v;\n+------------+\n| VAR_POP(i) |\n+------------+\n| 200.5000 |\n+------------+\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n ... [VAR_SAMP] declaration=expr category=Functions and Modifiers for Use with GROUP BY -description=Returns the sample variance of expr. That is, the\ndenominator is the number of rows minus one.\n \nIt is an aggregate function, and so can be used with the\nGROUP BY clause.\n \nFrom MariaDB 10.2.2, VAR_SAMP() can be used as a window\nfunction.\n \nVAR_SAMP() returns NULL if there were no matching rows.\n \n\nAs an aggregate function:\n \nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n \nINSERT INTO stats VALUES \n (''a'',1),(''a'',2),(''a'',3),\n (''b'',11),(''b'',12),(''b'',20),(''b'',30),(''b'',60);\n \nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n \n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n \nAs a window function:\n \nCREATE OR REPLACE TABLE student_test (name CHAR(10), test\nCHAR(10), score TINYINT);\n \nINSERT INTO student_test VALUES \n (''Chun'', ''SQL'', 75), (''Chun'', ''Tuning'', 73), \n (''Esben'', ''SQL'', 43), (''Esben'', ''Tuning'', 31), \n (''Kaolin'', ''SQL'', 56), (''Kaolin'', ''Tuning'', 88), \n (''Tatiana'', ''SQL'', 87);\n \nSELECT name, test, score, VAR_SAMP(score) \n OVER (PARTITION BY test) AS variance_results FROM\nstudent_test;\n \n+---------+--------+-------+------------------+\n| name | test | score | variance_results |\n+---------+--------+-------+------------------+\n| Chun | SQL | 75 | 382.9167 |\n| Chun | Tuning | 73 | 873.0000 |\n| Esben | SQL | 43 | 382.9167 |\n| Esben | Tuning | 31 | 873.0000 |\n| Kaolin | SQL | 56 | 382.9167 |\n| Kaolin | Tuning | 88 | 873.0000 |\n| Tatiana | SQL | 87 | 382.9167 |\n+---------+--------+-------+------------------+ +description=Returns the sample variance of expr. That is, the denominator is the number of\nrows minus one.\n\nIt is an aggregate function, and so can be used with the GROUP BY clause.\n\nVAR_SAMP() can be used as a window function.\n\nVAR_SAMP() returns NULL if there were no matching rows.\n\nExamples\n--------\n\nAs an aggregate function:\n\nCREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT);\n\nINSERT INTO stats VALUES \n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);\n\nSELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) \n FROM stats GROUP BY category;\n+----------+---------------+----------------+------------+\n| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |\n+----------+---------------+----------------+------------+\n| a | 0.8165 | 1.0000 | 0.6667 |\n| b | 18.0400 | 20.1693 | 325.4400 |\n+----------+---------------+----------------+------------+\n\nAs a window function:\n\nCREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), score\nTINYINT);\n\nINSERT INTO student_test VALUES \n ('Chun', 'SQL', 75), ('Chun', 'Tuning', 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87);\n\nSELECT name, test, score, VAR_SAMP(score) \n OVER (PARTITION BY test) AS variance_results FROM student_test;\n+---------+--------+-------+------------------+\n| name | test | score | variance_results |\n+---------+--------+-------+------------------+\n| Chun | SQL | 75 | 382.9167 |\n| Chun | Tuning | 73 | 873.0000 |\n| Esben | SQL | 43 | 382.9167 |\n| Esben | Tuning | 31 | 873.0000 |\n| Kaolin | SQL | 56 | 382.9167 |\n ... [VERSION] declaration= category=Information Functions -description=Returns a string that indicates the MariaDB server version.\nThe string\nuses the utf8 character set.\n \n\nSELECT VERSION();\n+------------------------------+\n| VERSION() |\n+------------------------------+\n| 10.0.3-MariaDB-1~precise-log |\n+------------------------------+\n \nThe VERSION() string may have one or more of the following\nsuffixes:\n \nSuffix | Description | \n \n-embedded | The server is an embedded server (libmysqld). | \n \n-log | General logging, slow logging or binary (replication)\nlogging is enabled. | \n \n-debug | The server is compiled for debugging. | \n \n-valgrind | The server is compiled to be instrumented with\nvalgrind. | \n \nChanging the Version String\n \nSome old legacy code may break because they are parsing the\nVERSION string and expecting a MySQL string or a simple\nversion\nstring like Joomla til API17, see MDEV-7780.\n \nIn MariaDB 10.2 one can fool these applications by setting\nthe version string from the command line or the my.cnf files\nwith --version=.... +description=Returns a string that indicates the MariaDB server version. The string uses\nthe utf8 character set.\n\nExamples\n--------\n\nSELECT VERSION();\n+----------------+\n| VERSION() |\n+----------------+\n| 10.4.7-MariaDB |\n+----------------+\n\nThe VERSION() string may have one or more of the following suffixes:\n\n+---------------------------+------------------------------------------------+\n| Suffix | Description |\n+---------------------------+------------------------------------------------+\n| -embedded | The server is an embedded server |\n| | (libmariadbd). |\n+---------------------------+------------------------------------------------+\n| -log | General logging, slow logging or binary |\n| | (replication) logging is enabled. |\n+---------------------------+------------------------------------------------+\n| -debug | The server is compiled for debugging. |\n+---------------------------+------------------------------------------------+\n| -valgrind | The server is compiled to be instrumented |\n| | with valgrind. |\n+---------------------------+------------------------------------------------+\n\nChanging the Version String\n---------------------------\n\nSome old legacy code may break because they are parsing the VERSION string and\nexpecting a MySQL string or a simple version string like Joomla til API17, see\nMDEV-7780.\n\nOne can fool these applications by setting the version string from the command\nline or the my.cnf files with --version=....\n\nURL: https://mariadb.com/kb/en/version/ +[WEEK] +declaration=date[,mode] +category=Date and Time Functions +description=This function returns the week number for date. The two-argument form of\nWEEK() allows you to specify whether the week starts on Sunday or Monday and\nwhether the return value should be in the range from 0 to 53 or from 1 to 53.\nIf the mode argument is omitted, the value of the default_week_format system\nvariable is used.\n\nModes\n-----\n\n+-------+---------------------+--------+------------------------------------+\n| Mode | 1st day of week | Range | Week 1 is the 1st week with |\n+-------+---------------------+--------+------------------------------------+\n| 0 | Sunday | 0-53 | a Sunday in this year |\n+-------+---------------------+--------+------------------------------------+\n| 1 | Monday | 0-53 | more than 3 days this year |\n+-------+---------------------+--------+------------------------------------+\n| 2 | Sunday | 1-53 | a Sunday in this year |\n+-------+---------------------+--------+------------------------------------+\n| 3 | Monday | 1-53 | more than 3 days this year |\n+-------+---------------------+--------+------------------------------------+\n| 4 | Sunday | 0-53 | more than 3 days this year |\n+-------+---------------------+--------+------------------------------------+\n| 5 | Monday | 0-53 | a Monday in this year |\n+-------+---------------------+--------+------------------------------------+\n| 6 | Sunday | 1-53 | more than 3 days this year |\n+-------+---------------------+--------+------------------------------------+\n| 7 | Monday | 1-53 | a Monday in this year |\n+-------+---------------------+--------+------------------------------------+\n\nWith the mode value of 3, which means 'more than 3 days this year', weeks are\nnumbered according to ISO 8601:1988.\n\nExamples\n--------\n\nSELECT WEEK('2008-02-20');\n+--------------------+\n| WEEK('2008-02-20') |\n+--------------------+\n| 7 |\n+--------------------+\n\nSELECT WEEK('2008-02-20',0);\n+----------------------+\n| WEEK('2008-02-20',0) |\n+----------------------+\n| 7 |\n+----------------------+\n\nSELECT WEEK('2008-02-20',1);\n ... [WEEKDAY] declaration=date category=Date and Time Functions -description=Returns the weekday index for date \n(0 = Monday, 1 = Tuesday, ... 6 = Sunday).\n \nThis contrasts with DAYOFWEEK() which follows the ODBC\nstandard\n(1 = Sunday, 2 = Monday, ..., 7 = Saturday).\n \n\nSELECT WEEKDAY(''2008-02-03 22:23:00'');\n+--------------------------------+\n| WEEKDAY(''2008-02-03 22:23:00'') |\n+--------------------------------+\n| 6 |\n+--------------------------------+\n \nSELECT WEEKDAY(''2007-11-06'');\n+-----------------------+\n| WEEKDAY(''2007-11-06'') |\n+-----------------------+\n| 1 |\n+-----------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT d FROM t1 where WEEKDAY(d) = 6;\n \n+---------------------+\n| d |\n+---------------------+\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n+---------------------+ +description=Returns the weekday index for date (0 = Monday, 1 = Tuesday, ... 6 = Sunday).\n\nThis contrasts with DAYOFWEEK() which follows the ODBC standard (1 = Sunday, 2\n= Monday, ..., 7 = Saturday).\n\nExamples\n--------\n\nSELECT WEEKDAY('2008-02-03 22:23:00');\n+--------------------------------+\n| WEEKDAY('2008-02-03 22:23:00') |\n+--------------------------------+\n| 6 |\n+--------------------------------+\n\nSELECT WEEKDAY('2007-11-06');\n+-----------------------+\n| WEEKDAY('2007-11-06') |\n+-----------------------+\n| 1 |\n+-----------------------+\n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n\nSELECT d FROM t1 where WEEKDAY(d) = 6;\n+---------------------+\n| d |\n+---------------------+\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n+---------------------+\n\nURL: https://mariadb.com/kb/en/weekday/ [WEEKOFYEAR] declaration=date category=Date and Time Functions -description=Returns the calendar week of the date as a number in the\nrange from 1\nto 53. WEEKOFYEAR() is a compatibility function that is\nequivalent to\nWEEK(date,3).\n \n\nSELECT WEEKOFYEAR(''2008-02-20'');\n+--------------------------+\n| WEEKOFYEAR(''2008-02-20'') |\n+--------------------------+\n| 8 |\n+--------------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \n select * from t1;\n \n+---------------------+\n| d |\n+---------------------+\n| 2007-01-30 21:31:07 |\n| 1983-10-15 06:42:51 |\n| 2011-04-21 12:34:56 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n| 2004-10-07 11:19:34 |\n+---------------------+\n \nSELECT d, WEEKOFYEAR(d), WEEK(d,3) from t1;\n \n+---------------------+---------------+-----------+\n| d | WEEKOFYEAR(d) | WEEK(d,3) |\n+---------------------+---------------+-----------+\n| 2007-01-30 21:31:07 | 5 | 5 |\n| 1983-10-15 06:42:51 | 41 | 41 |\n| 2011-04-21 12:34:56 | 16 | 16 |\n| 2011-10-30 06:31:41 | 43 | 43 |\n| 2011-01-30 14:03:25 | 4 | 4 |\n| 2004-10-07 11:19:34 | 41 | 41 |\n+---------------------+---------------+-----------+ -[WEEK] -declaration=date[,mode] -category=Date and Time Functions -description=This function returns the week number for date. The\ntwo-argument form of\nWEEK() allows you to specify whether the week starts on\nSunday or Monday\nand whether the return value should be in the range from 0\nto 53 or from 1 to\n53. If the mode argument is omitted, the value of the\ndefault_week_format system variable is used.\n \nModes\n \nMode | 1st day of week | Range | Week 1 is the 1st week with\n| \n \n0 | Sunday | 0-53 | a Sunday in this year | \n \n1 | Monday | 0-53 | more than 3 days this year | \n \n2 | Sunday | 1-53 | a Sunday in this year | \n \n3 | Monday | 1-53 | more than 3 days this year | \n \n4 | Sunday | 0-53 | more than 3 days this year | \n \n5 | Monday | 0-53 | a Monday in this year | \n \n6 | Sunday | 1-53 | more than 3 days this year | \n \n7 | Monday | 1-53 | a Monday in this year | \n \n\nSELECT WEEK(''2008-02-20'');\n+--------------------+\n| WEEK(''2008-02-20'') |\n+--------------------+\n| 7 |\n+--------------------+\n \nSELECT WEEK(''2008-02-20'',0);\n+----------------------+\n| WEEK(''2008-02-20'',0) |\n+----------------------+\n| 7 |\n+----------------------+\n \nSELECT WEEK(''2008-02-20'',1);\n+----------------------+\n| WEEK(''2008-02-20'',1) |\n+----------------------+\n| 8 |\n+----------------------+\n \nSELECT WEEK(''2008-12-31'',0);\n+----------------------+\n| WEEK(''2008-12-31'',0) |\n+----------------------+\n| 52 |\n+----------------------+\n \nSELECT WEEK(''2008-12-31'',1);\n+----------------------+\n| WEEK(''2008-12-31'',1) |\n+----------------------+\n| 53 |\n+----------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT d, WEEK(d,0), WEEK(d,1) from t1;\n \n+---------------------+-----------+-----------+\n| d | WEEK(d,0) | WEEK(d,1) |\n+---------------------+-----------+-----------+\n| 2007-01-30 21:31:07 | 4 | 5 |\n| 1983-10-15 06:42:51 | 41 | 41 |\n| 2011-04-21 12:34:56 | 16 | 16 |\n| 2011-10-30 06:31:41 | 44 | 43 |\n| 2011-01-30 14:03:25 | 5 | 4 |\n| 2004-10-07 11:19:34 | 40 | 41 |\n+---------------------+-----------+-----------+ +description=Returns the calendar week of the date as a number in the range from 1 to 53.\nWEEKOFYEAR() is a compatibility function that is equivalent to WEEK(date,3).\n\nExamples\n--------\n\nSELECT WEEKOFYEAR('2008-02-20');\n+--------------------------+\n| WEEKOFYEAR('2008-02-20') |\n+--------------------------+\n| 8 |\n+--------------------------+\n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n\nselect * from t1;\n+---------------------+\n| d |\n+---------------------+\n| 2007-01-30 21:31:07 |\n| 1983-10-15 06:42:51 |\n| 2011-04-21 12:34:56 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n| 2004-10-07 11:19:34 |\n+---------------------+\n\nSELECT d, WEEKOFYEAR(d), WEEK(d,3) from t1;\n+---------------------+---------------+-----------+\n| d | WEEKOFYEAR(d) | WEEK(d,3) |\n+---------------------+---------------+-----------+\n| 2007-01-30 21:31:07 | 5 | 5 |\n| 1983-10-15 06:42:51 | 41 | 41 |\n| 2011-04-21 12:34:56 | 16 | 16 |\n| 2011-10-30 06:31:41 | 43 | 43 |\n| 2011-01-30 14:03:25 | 4 | 4 |\n| 2004-10-07 11:19:34 | 41 | 41 |\n+---------------------+---------------+-----------+\n\nURL: https://mariadb.com/kb/en/weekofyear/ [WEIGHT_STRING] -declaration=str [AS {CHAR|BINARY}(N)] [LEVEL levels] [flags] +declaration=str [AS {CHAR|BINARY}(N category=String Functions -description=Returns a binary string representing the string''s sorting\nand comparison value. A string with a lower result means\nthat for sorting purposes the string appears before a string\nwith a higher result.\n \nWEIGHT_STRING() is particularly useful when adding new\ncollations, for testing purposes.\n \nIf str is a non-binary string (CHAR, VARCHAR or TEXT),\nWEIGHT_STRING returns the string''s collation weight. If str\nis a binary string (BINARY, VARBINARY or BLOB), the return\nvalue is simply the input value, since the weight for each\nbyte in a binary string is the byte value.\n \nWEIGHT_STRING() returns NULL if given a NULL input. \n \nThe optional AS clause permits casting the input string to a\nbinary or non-binary string, as well as to a particular\nlength.\n \nAS BINARY(N) measures the length in bytes rather than\ncharacters, and right pads with 0x00 bytes to the desired\nlength. \n \nAS CHAR(N) measures the length in characters, and right pads\nwith spaces to the desired length.\n \nN has a minimum value of 1, and if it is less than the\nlength of the input string, the string is truncated without\nwarning.\n \nThe optional LEVEL clause specifies that the return value\nshould contain weights for specific collation levels. The\nlevels specifier can either be a single integer, a\ncomma-separated list of integers, or a range of integers\nseparated by a dash (whitespace is ignored). Integers can\nrange from 1 to a maximum of 6, dependent on the collation,\nand need to be listed in ascending order.\n \nIf the LEVEL clause is no provided, a default of 1 to the\nmaximum for the collation is assumed.\n \nIf the LEVEL is specified without using a range, an optional\nmodifier is permitted.\n \nASC, the default, returns the weights without any\nmodification.\n \nDESC returns bitwise-inverted weights.\n \nREVERSE returns the weights in reverse order.\n \n\nThe examples below use the HEX() function to represent\nnon-printable results in hexadecimal format.\n \nSELECT HEX(WEIGHT_STRING(''x''));\n+-------------------------+\n| HEX(WEIGHT_STRING(''x'')) |\n+-------------------------+\n| 0058 |\n+-------------------------+\n \nSELECT HEX(WEIGHT_STRING(''x'' AS BINARY(4)));\n+--------------------------------------+\n| HEX(WEIGHT_STRING(''x'' AS BINARY(4))) |\n+--------------------------------------+\n| 78000000 |\n+--------------------------------------+\n \nSELECT HEX(WEIGHT_STRING(''x'' AS CHAR(4)));\n+------------------------------------+\n| HEX(WEIGHT_STRING(''x'' AS CHAR(4))) |\n+------------------------------------+\n| 0058002000200020 |\n+------------------------------------+\n \nSELECT HEX(WEIGHT_STRING(0xaa22ee LEVEL 1));\n+--------------------------------------+\n| HEX(WEIGHT_STRING(0xaa22ee LEVEL 1)) |\n+--------------------------------------+\n| AA22EE |\n+--------------------------------------+\n \nSELECT HEX(WEIGHT_STRING(0xaa22ee LEVEL 1 DESC));\n+-------------------------------------------+\n| HEX(WEIGHT_STRING(0xaa22ee LEVEL 1 DESC)) |\n+-------------------------------------------+\n| 55DD11 |\n+-------------------------------------------+\n \nSELECT HEX(WEIGHT_STRING(0xaa22ee LEVEL 1 REVERSE));\n+----------------------------------------------+\n| HEX(WEIGHT_STRING(0xaa22ee LEVEL 1 REVERSE)) |\n+----------------------------------------------+\n| EE22AA |\n+----------------------------------------------+ +description=Returns a binary string representing the string's sorting and comparison\nvalue. A string with a lower result means that for sorting purposes the string\nappears before a string with a higher result.\n\nWEIGHT_STRING() is particularly useful when adding new collations, for testing\npurposes.\n\nIf str is a non-binary string (CHAR, VARCHAR or TEXT), WEIGHT_STRING returns\nthe string's collation weight. If str is a binary string (BINARY, VARBINARY or\nBLOB), the return value is simply the input value, since the weight for each\nbyte in a binary string is the byte value.\n\nWEIGHT_STRING() returns NULL if given a NULL input.\n\nThe optional AS clause permits casting the input string to a binary or\nnon-binary string, as well as to a particular length.\n\nAS BINARY(N) measures the length in bytes rather than characters, and right\npads with 0x00 bytes to the desired length.\n\nAS CHAR(N) measures the length in characters, and right pads with spaces to\nthe desired length.\n\nN has a minimum value of 1, and if it is less than the length of the input\nstring, the string is truncated without warning.\n\nThe optional LEVEL clause specifies that the return value should contain\nweights for specific collation levels. The levels specifier can either be a\nsingle integer, a comma-separated list of integers, or a range of integers\nseparated by a dash (whitespace is ignored). Integers can range from 1 to a\nmaximum of 6, dependent on the collation, and need to be listed in ascending\norder.\n\nIf the LEVEL clause is no provided, a default of 1 to the maximum for the\ncollation is assumed.\n\nIf the LEVEL is specified without using a range, an optional modifier is\npermitted.\n\nASC, the default, returns the weights without any modification.\n\nDESC returns bitwise-inverted weights.\n\nREVERSE returns the weights in reverse order.\n\nExamples\n--------\n\nThe examples below use the HEX() function to represent non-printable results\nin hexadecimal format.\n ... [WITHIN] declaration=g1,g2 category=Geometry Relations -description=Returns 1 or 0 to indicate whether g1 is spatially within\ng2.\nThis tests the opposite relationship as Contains().\n \nWITHIN() is based on the original MySQL implementation, and\nuses object bounding rectangles, while ST_WITHIN() uses\nobject shapes.\n \n\nSET @g1 = GEOMFROMTEXT(''POINT(174 149)'');\nSET @g2 = GEOMFROMTEXT(''POINT(176 151)'');\nSET @g3 = GEOMFROMTEXT(''POLYGON((175 150, 20 40, 50 60, 125\n100, 175 150))'');\n \nSELECT within(@g1,@g3);\n+-----------------+\n| within(@g1,@g3) |\n+-----------------+\n| 1 |\n+-----------------+\n \nSELECT within(@g2,@g3);\n+-----------------+\n| within(@g2,@g3) |\n+-----------------+\n| 0 |\n+-----------------+ -[YEARWEEK1] -name=YEARWEEK +description=Returns 1 or 0 to indicate whether g1 is spatially within g2. This tests the\nopposite relationship as Contains().\n\nWITHIN() is based on the original MySQL implementation, and uses object\nbounding rectangles, while ST_WITHIN() uses object shapes.\n\nExamples\n--------\n\nSET @g1 = GEOMFROMTEXT('POINT(174 149)');\nSET @g2 = GEOMFROMTEXT('POINT(176 151)');\nSET @g3 = GEOMFROMTEXT('POLYGON((175 150, 20 40, 50 60, 125 100, 175 150))');\n\nSELECT within(@g1,@g3);\n+-----------------+\n| within(@g1,@g3) |\n+-----------------+\n| 1 |\n+-----------------+\n\nSELECT within(@g2,@g3);\n+-----------------+\n| within(@g2,@g3) |\n+-----------------+\n| 0 |\n+-----------------+\n\nURL: https://mariadb.com/kb/en/within/ +[WSREP_LAST_SEEN_GTID] +declaration= +category=Galera Functions +description=Returns the Global Transaction ID of the most recent write transaction\nobserved by the client.\n\nThe result can be useful to determine the transaction to provide to\nWSREP_SYNC_WAIT_UPTO_GTID for waiting and unblocking purposes.\n\nURL: https://mariadb.com/kb/en/wsrep_last_seen_gtid/ +[WSREP_LAST_WRITTEN_GTID] +declaration= +category=Galera Functions +description=Returns the Global Transaction ID of the most recent write transaction\nperformed by the client.\n\nURL: https://mariadb.com/kb/en/wsrep_last_written_gtid/ +[WSREP_SYNC_WAIT_UPTO_GTID] +declaration=gtid[,timeout] +category=Galera Functions +description=Blocks the client until the transaction specified by the given Global\nTransaction ID is applied and committed by the node.\n\nThe optional timeout argument can be used to specify a block timeout in\nseconds. If not provided, the timeout will be indefinite.\n\nReturns the node that applied and committed the Global Transaction ID,\nER_LOCAL_WAIT_TIMEOUT if the function is timed out before this, or\nER_WRONG_ARGUMENTS if the function is given an invalid GTID.\n\nThe result from WSREP_LAST_SEEN_GTID can be useful to determine the\ntransaction to provide to WSREP_SYNC_WAIT_UPTO_GTID for waiting and unblocking\npurposes.\n\nURL: https://mariadb.com/kb/en/wsrep_sync_wait_upto_gtid/ +[YEAR] declaration=date category=Date and Time Functions -description=Returns year and week for a date. The mode argument works\nexactly like the mode\nargument to WEEK(). The year in the result may be different\nfrom the\nyear in the date argument for the first and the last week of\nthe year.\n \n\nSELECT YEARWEEK(''1987-01-01'');\n+------------------------+\n| YEARWEEK(''1987-01-01'') |\n+------------------------+\n| 198652 |\n+------------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT * FROM t1;\n \n+---------------------+\n| d |\n+---------------------+\n| 2007-01-30 21:31:07 |\n| 1983-10-15 06:42:51 |\n| 2011-04-21 12:34:56 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n| 2004-10-07 11:19:34 |\n+---------------------+\n6 rows in set (0.02 sec)\n \nSELECT YEARWEEK(d) FROM t1 WHERE YEAR(d) = 2011;\n \n+-------------+\n| YEARWEEK(d) |\n+-------------+\n| 201116 |\n| 201144 |\n| 201105 |\n+-------------+\n3 rows in set (0.03 sec) -[YEARWEEK2] -name=YEARWEEK -declaration=date,mode -category=Date and Time Functions -description=Returns year and week for a date. The mode argument works\nexactly like the mode\nargument to WEEK(). The year in the result may be different\nfrom the\nyear in the date argument for the first and the last week of\nthe year.\n \n\nSELECT YEARWEEK(''1987-01-01'');\n+------------------------+\n| YEARWEEK(''1987-01-01'') |\n+------------------------+\n| 198652 |\n+------------------------+\n \nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT * FROM t1;\n \n+---------------------+\n| d |\n+---------------------+\n| 2007-01-30 21:31:07 |\n| 1983-10-15 06:42:51 |\n| 2011-04-21 12:34:56 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n| 2004-10-07 11:19:34 |\n+---------------------+\n6 rows in set (0.02 sec)\n \nSELECT YEARWEEK(d) FROM t1 WHERE YEAR(d) = 2011;\n \n+-------------+\n| YEARWEEK(d) |\n+-------------+\n| 201116 |\n| 201144 |\n| 201105 |\n+-------------+\n3 rows in set (0.03 sec) -[YEAR] +description=Returns the year for the given date, in the range 1000 to 9999, or 0 for the\n"zero" date.\n\nExamples\n--------\n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n\nSELECT * FROM t1;\n+---------------------+\n| d |\n+---------------------+\n| 2007-01-30 21:31:07 |\n| 1983-10-15 06:42:51 |\n| 2011-04-21 12:34:56 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n| 2004-10-07 11:19:34 |\n+---------------------+\n\nSELECT * FROM t1 WHERE YEAR(d) = 2011;\n+---------------------+\n| d |\n+---------------------+\n| 2011-04-21 12:34:56 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n+---------------------+\n\nSELECT YEAR('1987-01-01');\n+--------------------+\n| YEAR('1987-01-01') |\n+--------------------+\n| 1987 |\n+--------------------+\n\nURL: https://mariadb.com/kb/en/year/ +[YEARWEEK] declaration=date category=Date and Time Functions -description=Returns the year for the given date, in the range 1000 to\n9999, or 0 for the\n"zero" date.\n \n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n \nSELECT * FROM t1;\n \n+---------------------+\n| d |\n+---------------------+\n| 2007-01-30 21:31:07 |\n| 1983-10-15 06:42:51 |\n| 2011-04-21 12:34:56 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n| 2004-10-07 11:19:34 |\n+---------------------+\n \nSELECT * FROM t1 WHERE YEAR(d) = 2011;\n \n+---------------------+\n| d |\n+---------------------+\n| 2011-04-21 12:34:56 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n+---------------------+\n \nSELECT YEAR(''1987-01-01'');\n+--------------------+\n| YEAR(''1987-01-01'') |\n+--------------------+\n| 1987 |\n+--------------------+ \ No newline at end of file +description=Returns year and week for a date. The mode argument works exactly like the\nmode argument to WEEK(). The year in the result may be different from the year\nin the date argument for the first and the last week of the year.\n\nExamples\n--------\n\nSELECT YEARWEEK('1987-01-01');\n+------------------------+\n| YEARWEEK('1987-01-01') |\n+------------------------+\n| 198652 |\n+------------------------+\n\nCREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n ("2007-01-30 21:31:07"),\n ("1983-10-15 06:42:51"),\n ("2011-04-21 12:34:56"),\n ("2011-10-30 06:31:41"),\n ("2011-01-30 14:03:25"),\n ("2004-10-07 11:19:34");\n\nSELECT * FROM t1;\n+---------------------+\n| d |\n+---------------------+\n| 2007-01-30 21:31:07 |\n| 1983-10-15 06:42:51 |\n| 2011-04-21 12:34:56 |\n| 2011-10-30 06:31:41 |\n| 2011-01-30 14:03:25 |\n| 2004-10-07 11:19:34 |\n+---------------------+\n6 rows in set (0.02 sec)\n\nSELECT YEARWEEK(d) FROM t1 WHERE YEAR(d) = 2011;\n+-------------+\n| YEARWEEK(d) |\n+-------------+\n| 201116 |\n| 201144 |\n| 201105 |\n+-------------+\n3 rows in set (0.03 sec)\n\nURL: https://mariadb.com/kb/en/yearweek/ \ No newline at end of file diff --git a/out/functions-mysql.ini b/out/functions-mysql.ini index 4d8f4a233..9a47ab288 100644 --- a/out/functions-mysql.ini +++ b/out/functions-mysql.ini @@ -1,1363 +1,1340 @@ [ABS] declaration=X category=Numeric Functions -description=Returns the absolute value of X, or NULL if X is NULL +description=Returns the absolute value of X.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [ACOS] declaration=X category=Numeric Functions -description=Returns the arc cosine of X, that is, the value whose cosine is X +description=Returns the arc cosine of X, that is, the value whose cosine is X.\nReturns NULL if X is not in the range -1 to 1.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [ADDDATE] declaration=date,INTERVAL expr unit category=Date and Time Functions -description=When invoked with the INTERVAL form of the second argument, ADDDATE() is a\nsynonym for DATE_ADD() +description=When invoked with the INTERVAL form of the second argument, ADDDATE()\nis a synonym for DATE_ADD(). The related function SUBDATE() is a\nsynonym for DATE_SUB(). For information on the INTERVAL unit argument,\nsee the discussion for DATE_ADD().\n\nmysql> SELECT DATE_ADD('2008-01-02', INTERVAL 31 DAY);\n -> '2008-02-02'\nmysql> SELECT ADDDATE('2008-01-02', INTERVAL 31 DAY);\n -> '2008-02-02'\n\nWhen invoked with the days form of the second argument, MySQL treats it\nas an integer number of days to be added to expr.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [ADDTIME] declaration=expr1,expr2 category=Date and Time Functions -description=ADDTIME() adds expr2 to expr1 and returns the result +description=ADDTIME() adds expr2 to expr1 and returns the result. expr1 is a time\nor datetime expression, and expr2 is a time expression.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [AES_DECRYPT] declaration=crypt_str,key_str[,init_vector] category=Encryption Functions -description=This function decrypts data using the official AES (Advanced Encryption\nStandard) algorithm +description=This function decrypts data using the official AES (Advanced Encryption\nStandard) algorithm. For more information, see the description of\nAES_ENCRYPT().\n\nThe optional initialization vector argument, init_vector, is available\nas of MySQL 5.7.4. As of that version, statements that use\nAES_DECRYPT() are unsafe for statement-based replication and cannot be\nstored in the query cache.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html [AES_ENCRYPT] declaration=str,key_str[,init_vector] category=Encryption Functions -description=AES_ENCRYPT() and AES_DECRYPT() implement encryption and decryption of data\nusing the official AES (Advanced Encryption Standard) algorithm, previously\nknown as "Rijndael +description=AES_ENCRYPT() and AES_DECRYPT() implement encryption and decryption of\ndata using the official AES (Advanced Encryption Standard) algorithm,\npreviously known as "Rijndael." The AES standard permits various key\nlengths. By default these functions implement AES with a 128-bit key\nlength. As of MySQL 5.7.4, key lengths of 196 or 256 bits can be used,\nas described later. The key length is a trade off between performance\nand security.\n\nAES_ENCRYPT() encrypts the string str using the key string key_str and\nreturns a binary string containing the encrypted output. AES_DECRYPT()\ndecrypts the encrypted string crypt_str using the key string key_str\nand returns the original plaintext string. If either function argument\nis NULL, the function returns NULL.\n\nThe str and crypt_str arguments can be any length, and padding is\nautomatically added to str so it is a multiple of a block as required\nby block-based algorithms such as AES. This padding is automatically\nremoved by the AES_DECRYPT() function. The length of crypt_str can be\ncalculated using this formula:\n\n16 * (trunc(string_length / 16) + 1)\n\nFor a key length of 128 bits, the most secure way to pass a key to the\nkey_str argument is to create a truly random 128-bit value and pass it\nas a binary value. For example:\n\nINSERT INTO t\nVALUES (1,AES_ENCRYPT('text',UNHEX('F3229A0B371ED2D9441B830D21A390C3')));\n\nA passphrase can be used to generate an AES key by hashing the\npassphrase. For example:\n\nINSERT INTO t VALUES (1,AES_ENCRYPT('text', SHA2('My secret passphrase',512)));\n\nDo not pass a password or passphrase directly to crypt_str, hash it\nfirst. Previous versions of this documentation suggested the former\napproach, but it is no longer recommended as the examples shown here\nare more secure.\n\nIf AES_DECRYPT() detects invalid data or incorrect padding, it returns\nNULL. However, it is possible for AES_DECRYPT() to return a non-NULL\nvalue (possibly garbage) if the input data or the key is invalid.\n\nAs of MySQL 5.7.4, AES_ENCRYPT() and AES_DECRYPT() permit control of\nthe block encryption mode and take an optional init_vector\ninitialization vector argument:\n\no The block_encryption_mode system variable controls the mode for\n block-based encryption algorithms. Its default value is aes-128-ecb,\n which signifies encryption using a key length of 128 bits and ECB\n ... [ANY_VALUE] declaration=arg category=Miscellaneous Functions -description=This function is useful for GROUP BY queries when the ONLY_FULL_GROUP_BY\nSQL mode is enabled, for cases when MySQL rejects a query that you know is\nvalid for reasons that MySQL cannot determine +description=This function is useful for GROUP BY queries when the\nONLY_FULL_GROUP_BY SQL mode is enabled, for cases when MySQL rejects a\nquery that you know is valid for reasons that MySQL cannot determine.\nThe function return value and type are the same as the return value and\ntype of its argument, but the function result is not checked for the\nONLY_FULL_GROUP_BY SQL mode.\n\nFor example, if name is a nonindexed column, the following query fails\nwith ONLY_FULL_GROUP_BY enabled:\n\nmysql> SELECT name, address, MAX(age) FROM t GROUP BY name;\nERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP\nBY clause and contains nonaggregated column 'mydb.t.address' which\nis not functionally dependent on columns in GROUP BY clause; this\nis incompatible with sql_mode=only_full_group_by\n\nThe failure occurs because address is a nonaggregated column that is\nneither named among GROUP BY columns nor functionally dependent on\nthem. As a result, the address value for rows within each name group is\nnondeterministic. There are multiple ways to cause MySQL to accept the\nquery:\n\no Alter the table to make name a primary key or a unique NOT NULL\n column. This enables MySQL to determine that address is functionally\n dependent on name; that is, address is uniquely determined by name.\n (This technique is inapplicable if NULL must be permitted as a valid\n name value.)\n\no Use ANY_VALUE() to refer to address:\n\nSELECT name, ANY_VALUE(address), MAX(age) FROM t GROUP BY name;\n\n In this case, MySQL ignores the nondeterminism of address values\n within each name group and accepts the query. This may be useful if\n you simply do not care which value of a nonaggregated column is\n chosen for each group. ANY_VALUE() is not an aggregate function,\n unlike functions such as SUM() or COUNT(). It simply acts to suppress\n the test for nondeterminism.\n\no Disable ONLY_FULL_GROUP_BY. This is equivalent to using ANY_VALUE()\n with ONLY_FULL_GROUP_BY enabled, as described in the previous item.\n\nANY_VALUE() is also useful if functional dependence exists between\ncolumns but MySQL cannot determine it. The following query is valid\nbecause age is functionally dependent on the grouping column age-1, but\nMySQL cannot tell that and rejects the query with ONLY_FULL_GROUP_BY\nenabled:\n\nSELECT age FROM t GROUP BY age-1;\n\n ... +[AREA] +declaration=poly +category=Polygon properties +description=ST_Area() and Area() are synonyms. For more information, see the\ndescription of ST_Area().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-polygon-property-functions.html +[ASBINARY] +declaration=g +category=WKB +description=Converts a value in internal geometry format to its WKB representation\nand returns the binary result.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-format-conversion-functions.html [ASCII] declaration=str category=String Functions -description=Returns the numeric value of the leftmost character of the string str +description=Returns the numeric value of the leftmost character of the string str.\nReturns 0 if str is the empty string. Returns NULL if str is NULL.\nASCII() works for 8-bit characters.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [ASIN] declaration=X category=Numeric Functions -description=Returns the arc sine of X, that is, the value whose sine is X -[ASYMMETRIC_DECRYPT] -declaration=algorithm, crypt_str, key_str -category=Enterprise Encryption Functions -description=Decrypts an encrypted string using the given algorithm and key string, and\nreturns the resulting plaintext as a binary string -[ASYMMETRIC_DERIVE] -declaration=pub_key_str, priv_key_str -category=Enterprise Encryption Functions -description=Derives a symmetric key using the private key of one party and the public\nkey of another, and returns the resulting key as a binary string -[ASYMMETRIC_ENCRYPT] -declaration=algorithm, str, key_str -category=Enterprise Encryption Functions -description=Encrypts a string using the given algorithm and key string, and returns the\nresulting ciphertext as a binary string -[ASYMMETRIC_SIGN] -declaration=algorithm, digest_str, priv_key_str, digest_type -category=Enterprise Encryption Functions -description=Signs a digest string using a private key string, and returns the signature\nas a binary string -[ASYMMETRIC_VERIFY] -declaration=algorithm, digest_str, sig_str, pub_key_str, -digest_type -category=Enterprise Encryption Functions -description=Verifies whether the signature string matches the digest string, and\nreturns 1 or 0 to indicate whether verification succeeded or failed +description=Returns the arc sine of X, that is, the value whose sine is X. Returns\nNULL if X is not in the range -1 to 1.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html +[ASTEXT] +declaration=g +category=WKT +description=Converts a value in internal geometry format to its WKT representation\nand returns the string result.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-format-conversion-functions.html [ATAN] declaration=X category=Numeric Functions -description=Returns the arc tangent of X, that is, the value whose tangent is X +description=Returns the arc tangent of X, that is, the value whose tangent is X.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html +[ATAN2] +declaration=Y,X +category=Numeric Functions +description=Returns the arc tangent of the two variables X and Y. It is similar to\ncalculating the arc tangent of Y / X, except that the signs of both\narguments are used to determine the quadrant of the result.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [AVG] declaration=[DISTINCT] expr -category=Aggregate Functions and Modifiers -description=Returns the average value of expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the average value of expr. The DISTINCT option can be used to\nreturn the average of the distinct values of expr.\n\nAVG() returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [BENCHMARK] declaration=count,expr category=Information Functions -description=The BENCHMARK() function executes the expression expr repeatedly count\ntimes +description=The BENCHMARK() function executes the expression expr repeatedly count\ntimes. It may be used to time how quickly MySQL processes the\nexpression. The result value is always 0. The intended use is from\nwithin the mysql client, which reports query execution times:\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html +[BIGINT] +declaration=M +category=Data Types +description=A large integer. The signed range is -9223372036854775808 to\n9223372036854775807. The unsigned range is 0 to 18446744073709551615.\n\nSERIAL is an alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html [BIN] declaration=N category=String Functions -description=Returns a string representation of the binary value of N, where N is a\nlonglong (BIGINT) number -[BIN_TO_UUID] -declaration=binary_uuid -category=Miscellaneous Functions -description=BIN_TO_UUID() is the inverse of UUID_TO_BIN() +description=Returns a string representation of the binary value of N, where N is a\nlonglong (BIGINT) number. This is equivalent to CONV(N,10,2). Returns\nNULL if N is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[BINARY] +declaration=M +category=Data Types +description=The BINARY type is similar to the CHAR type, but stores binary byte\nstrings rather than nonbinary character strings. M represents the\ncolumn length in bytes.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html +[BIT] +declaration=M +category=Data Types +description=A bit-field type. M indicates the number of bits per value, from 1 to\n64. The default is 1 if M is omitted.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html [BIT_AND] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns the bitwise AND of all bits in expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the bitwise AND of all bits in expr. The calculation is\nperformed with 64-bit (BIGINT) precision.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [BIT_COUNT] declaration=N category=Bit Functions -description=Returns the number of bits that are set in the argument N as an unsigned\n64-bit integer, or NULL if the argument is NULL +description=Returns the number of bits that are set in the argument N.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/bit-functions.html [BIT_LENGTH] declaration=str category=String Functions -description=Returns the length of the string str in bits +description=Returns the length of the string str in bits.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [BIT_OR] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns the bitwise OR of all bits in expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the bitwise OR of all bits in expr. The calculation is\nperformed with 64-bit (BIGINT) precision.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [BIT_XOR] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns the bitwise XOR of all bits in expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the bitwise XOR of all bits in expr. The calculation is\nperformed with 64-bit (BIGINT) precision.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html +[BLOB] +declaration=M +category=Data Types +description=A BLOB column with a maximum length of 65,535 (216 - 1) bytes. Each\nBLOB value is stored using a 2-byte length prefix that indicates the\nnumber of bytes in the value.\n\nAn optional length M can be given for this type. If this is done, MySQL\ncreates the column as the smallest BLOB type large enough to hold\nvalues M bytes long.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html +[BUFFER] +declaration=g,d +category=GeometryCollection properties +description=Returns a geometry that represents all points whose distance from the\ngeometry value g is less than or equal to a distance of d.\n\nBuffer() supports negative distances for polygons, multipolygons, and\ngeometry collections containing polygons or multipolygons. For point,\nmultipoint, linestring, multilinestring, and geometry collections not\ncontaining any polygons or multipolygons, Buffer() with a negative\ndistance returns NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-operator-functions.html [CAST] -declaration=expr AS type [ARRAY] -category=Cast Functions and Operators -description=The CAST() function takes an expression of any type and produces a result\nvalue of the specified type, similar to CONVERT() +declaration=expr AS type +category=String Functions +description=The CAST() function takes an expression of any type and produces a\nresult value of a specified type, similar to CONVERT(). See the\ndescription of CONVERT() for more information.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/cast-functions.html [CEIL] declaration=X category=Numeric Functions -description=CEIL() is a synonym for CEILING() +description=CEIL() is a synonym for CEILING().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [CEILING] declaration=X category=Numeric Functions -description=Returns the smallest integer value not less than X +description=Returns the smallest integer value not less than X.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html +[CENTROID] +declaration=mpoly +category=Polygon properties +description=ST_Centroid() and Centroid() are synonyms. For more information, see\nthe description of ST_Centroid().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-multipolygon-property-functions.html +[CHAR] +declaration=M +category=Data Types +description=collation_name]\n\nA fixed-length string that is always right-padded with spaces to the\nspecified length when stored. M represents the column length in\ncharacters. The range of M is 0 to 255. If M is omitted, the length is\n1.\n\n*Note*: Trailing spaces are removed when CHAR values are retrieved\nunless the PAD_CHAR_TO_FULL_LENGTH SQL mode is enabled.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html [CHARACTER_LENGTH] declaration=str category=String Functions -description=CHARACTER_LENGTH() is a synonym for CHAR_LENGTH() +description=CHARACTER_LENGTH() is a synonym for CHAR_LENGTH().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [CHARSET] declaration=str category=Information Functions -description=Returns the character set of the string argument +description=Returns the character set of the string argument.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [CHAR_LENGTH] declaration=str category=String Functions -description=Returns the length of the string str, measured in characters +description=Returns the length of the string str, measured in characters. A\nmultibyte character counts as a single character. This means that for a\nstring containing five 2-byte characters, LENGTH() returns 10, whereas\nCHAR_LENGTH() returns 5.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [COALESCE] declaration=value,... -category=Comparison Operators -description=Returns the first non-NULL value in the list, or NULL if there are no\nnon-NULL values +category=Comparison operators +description=Returns the first non-NULL value in the list, or NULL if there are no\nnon-NULL values.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html [COERCIBILITY] declaration=str category=Information Functions -description=Returns the collation coercibility value of the string argument +description=Returns the collation coercibility value of the string argument.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [COLLATION] declaration=str category=Information Functions -description=Returns the collation of the string argument +description=Returns the collation of the string argument.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [COMPRESS] declaration=string_to_compress category=Encryption Functions -description=Compresses a string and returns the result as a binary string +description=Compresses a string and returns the result as a binary string. This\nfunction requires MySQL to have been compiled with a compression\nlibrary such as zlib. Otherwise, the return value is always NULL. The\ncompressed string can be uncompressed with UNCOMPRESS().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html [CONCAT] declaration=str1,str2,... category=String Functions -description=Returns the string that results from concatenating the arguments +description=Returns the string that results from concatenating the arguments. May\nhave one or more arguments. If all arguments are nonbinary strings, the\nresult is a nonbinary string. If the arguments include any binary\nstrings, the result is a binary string. A numeric argument is converted\nto its equivalent nonbinary string form.\n\nCONCAT() returns NULL if any argument is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [CONCAT_WS] declaration=separator,str1,str2,... category=String Functions -description=CONCAT_WS() stands for Concatenate With Separator and is a special form of\nCONCAT() +description=CONCAT_WS() stands for Concatenate With Separator and is a special form\nof CONCAT(). The first argument is the separator for the rest of the\narguments. The separator is added between the strings to be\nconcatenated. The separator can be a string, as can the rest of the\narguments. If the separator is NULL, the result is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[CONNECTION_ID] +declaration= +category=Information Functions +description=Returns the connection ID (thread ID) for the connection. Every\nconnection has an ID that is unique among the set of currently\nconnected clients.\n\nThe value returned by CONNECTION_ID() is the same type of value as\ndisplayed in the ID column of the INFORMATION_SCHEMA.PROCESSLIST table,\nthe Id column of SHOW PROCESSLIST output, and the PROCESSLIST_ID column\nof the Performance Schema threads table.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html +[CONTAINS] +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 completely contains g2. This\ntests the opposite relationship as Within().\n\nThis function is deprecated as of MySQL 5.7.6 and will be removed in a\nfuture MySQL release. Use MBRContains() instead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html [CONV] declaration=N,from_base,to_base category=Numeric Functions -description=Converts numbers between different number bases +description=Converts numbers between different number bases. Returns a string\nrepresentation of the number N, converted from base from_base to base\nto_base. Returns NULL if any argument is NULL. The argument N is\ninterpreted as an integer, but may be specified as an integer or a\nstring. The minimum base is 2 and the maximum base is 36. If to_base is\na negative number, N is regarded as a signed number. Otherwise, N is\ntreated as unsigned. CONV() works with 64-bit precision.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [CONVERT] -declaration=expr USING transcoding_name -category=Cast Functions and Operators -description=The CONVERT() function takes an expression of any type and produces a\nresult value of the specified type +declaration=expr,type +category=String Functions +description=The CONVERT() and CAST() functions take an expression of any type and\nproduce a result value of a specified type.\n\nThe type for the result can be one of the following values:\n\no BINARY[(N)]\n\no CHAR[(N)]\n\no DATE\n\no DATETIME\n\no DECIMAL[(M[,D])]\n\no SIGNED [INTEGER]\n\no TIME\n\no UNSIGNED [INTEGER]\n\nBINARY produces a string with the BINARY data type. See\nhttp://dev.mysql.com/doc/refman/5.7/en/binary-varbinary.html for a\ndescription of how this affects comparisons. If the optional length N\nis given, BINARY(N) causes the cast to use no more than N bytes of the\nargument. Values shorter than N bytes are padded with 0x00 bytes to a\nlength of N.\n\nCHAR(N) causes the cast to use no more than N characters of the\nargument.\n\nCAST() and CONVERT(... USING ...) are standard SQL syntax. The\nnon-USING form of CONVERT() is ODBC syntax.\n\nCONVERT() with USING is used to convert data between different\ncharacter sets. In MySQL, transcoding names are the same as the\ncorresponding character set names. For example, this statement converts\nthe string 'abc' in the default character set to the corresponding\nstring in the utf8 character set:\n\nSELECT CONVERT('abc' USING utf8);\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/cast-functions.html [CONVERT_TZ] declaration=dt,from_tz,to_tz category=Date and Time Functions -description=CONVERT_TZ() converts a datetime value dt from the time zone given by\nfrom_tz to the time zone given by to_tz and returns the resulting value +description=CONVERT_TZ() converts a datetime value dt from the time zone given by\nfrom_tz to the time zone given by to_tz and returns the resulting\nvalue. Time zones are specified as described in\nhttp://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html. This\nfunction returns NULL if the arguments are invalid.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[CONVEXHULL] +declaration=g +category=GeometryCollection properties +description=ST_ConvexHull() and ConvexHull() are synonyms. For more information,\nsee the description of ST_ConvexHull().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-operator-functions.html [COS] declaration=X category=Numeric Functions -description=Returns the cosine of X, where X is given in radians +description=Returns the cosine of X, where X is given in radians.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [COT] declaration=X category=Numeric Functions -description=Returns the cotangent of X +description=Returns the cotangent of X.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [COUNT] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns a count of the number of non-NULL values of expr in the rows\nretrieved by a SELECT statement +category=Functions and Modifiers for Use with GROUP BY +description=Returns a count of the number of non-NULL values of expr in the rows\nretrieved by a SELECT statement. The result is a BIGINT value.\n\nCOUNT() returns 0 if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [CRC32] declaration=expr category=Numeric Functions -description=Computes a cyclic redundancy check value and returns a 32-bit unsigned\nvalue -[CREATE_ASYMMETRIC_PRIV_KEY] -declaration=algorithm, {key_len|dh_secret} -category=Enterprise Encryption Functions -description=Creates a private key using the given algorithm and key length or DH\nsecret, and returns the key as a binary string in PEM format -[CREATE_ASYMMETRIC_PUB_KEY] -declaration=algorithm, priv_key_str -category=Enterprise Encryption Functions -description=Derives a public key from the given private key using the given algorithm,\nand returns the key as a binary string in PEM format -[CREATE_DH_PARAMETERS] -declaration=key_len -category=Enterprise Encryption Functions -description=Creates a shared secret for generating a DH private/public key pair and\nreturns a binary string that can be passed to create_asymmetric_priv_key() -[CREATE_DIGEST] -declaration=digest_type, str -category=Enterprise Encryption Functions -description=Creates a digest from the given string using the given digest type, and\nreturns the digest as a binary string -[CURRENT_TIMESTAMP] -name=CURRENT_TIMESTAMP +description=Computes a cyclic redundancy check value and returns a 32-bit unsigned\nvalue. The result is NULL if the argument is NULL. The argument is\nexpected to be a string and (if possible) is treated as one if it is\nnot.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html +[CROSSES] +declaration=g1,g2 +category=Geometry relations +description=Returns 1 if g1 spatially crosses g2. Returns NULL if g1 is a Polygon\nor a MultiPolygon, or if g2 is a Point or a MultiPoint. Otherwise,\nreturns 0.\n\nThe term spatially crosses denotes a spatial relation between two given\ngeometries that has the following properties:\n\no The two geometries intersect\n\no Their intersection results in a geometry that has a dimension that is\n one less than the maximum dimension of the two given geometries\n\no Their intersection is not equal to either of the two given geometries\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html +[CURDATE] declaration= category=Date and Time Functions -description=CURRENT_TIMESTAMP and CURRENT_TIMESTAMP() are synonyms for NOW(). -[CURDATE] +description=Returns the current date as a value in 'YYYY-MM-DD' or YYYYMMDD format,\ndepending on whether the function is used in a string or numeric\ncontext.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[CURRENT_DATE] declaration= category=Date and Time Functions -description=Returns the current date as a value in ''YYYY-MM-DD'' or\nYYYYMMDD\nformat, depending on whether the function is used in a\nstring or\nnumeric context.\n \n\nSELECT CURDATE();\n+------------+\n| CURDATE() |\n+------------+\n| 2019-03-05 |\n+------------+\n \nIn a numeric context (note this is not performing date\ncalculations):\n \nSELECT CURDATE() +0;\n \n+--------------+\n| CURDATE() +0 |\n+--------------+\n| 20190305 |\n+--------------+\n \nData calculation:\n \nSELECT CURDATE() - INTERVAL 5 DAY;\n \n+----------------------------+\n| CURDATE() - INTERVAL 5 DAY |\n+----------------------------+\n| 2019-02-28 |\n+----------------------------+ +description=CURRENT_DATE and CURRENT_DATE() are synonyms for CURDATE().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[CURRENT_TIME] +declaration=[fsp] +category=Date and Time Functions +description=CURRENT_TIME and CURRENT_TIME() are synonyms for CURTIME().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[CURRENT_TIMESTAMP] +declaration=[fsp] +category=Date and Time Functions +description=CURRENT_TIMESTAMP and CURRENT_TIMESTAMP() are synonyms for NOW().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[CURRENT_USER] +declaration= +category=Information Functions +description=Returns the user name and host name combination for the MySQL account\nthat the server used to authenticate the current client. This account\ndetermines your access privileges. The return value is a string in the\nutf8 character set.\n\nThe value of CURRENT_USER() can differ from the value of USER().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [CURTIME] declaration=[fsp] category=Date and Time Functions -description=Returns the current time as a value in 'hh:mm:ss' or hhmmss format,\ndepending on whether the function is used in string or numeric context +description=Returns the current time as a value in 'HH:MM:SS' or HHMMSS format,\ndepending on whether the function is used in a string or numeric\ncontext. The value is expressed in the current time zone.\n\nIf the fsp argument is given to specify a fractional seconds precision\nfrom 0 to 6, the return value includes a fractional seconds part of\nthat many digits.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[DATABASE] +declaration= +category=Information Functions +description=Returns the default (current) database name as a string in the utf8\ncharacter set. If there is no default database, DATABASE() returns\nNULL. Within a stored routine, the default database is the database\nthat the routine is associated with, which is not necessarily the same\nas the database that is the default in the calling context.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [DATEDIFF] declaration=expr1,expr2 category=Date and Time Functions -description=DATEDIFF() returns expr1 - expr2 expressed as a value in days from one\ndate to the other +description=DATEDIFF() returns expr1 - expr2 expressed as a value in days from one\ndate to the other. expr1 and expr2 are date or date-and-time\nexpressions. Only the date parts of the values are used in the\ncalculation.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[DATETIME] +declaration=fsp +category=Data Types +description=A date and time combination. The supported range is '1000-01-01\n00:00:00.000000' to '9999-12-31 23:59:59.999999'. MySQL displays\nDATETIME values in 'YYYY-MM-DD HH:MM:SS[.fraction]' format, but permits\nassignment of values to DATETIME columns using either strings or\nnumbers.\n\nAn optional fsp value in the range from 0 to 6 may be given to specify\nfractional seconds precision. A value of 0 signifies that there is no\nfractional part. If omitted, the default precision is 0.\n\nAutomatic initialization and updating to the current date and time for\nDATETIME columns can be specified using DEFAULT and ON UPDATE column\ndefinition clauses, as described in\nhttp://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-overview.html [DATE_ADD] declaration=date,INTERVAL expr unit category=Date and Time Functions -description=These functions perform date arithmetic +description=These functions perform date arithmetic. The date argument specifies\nthe starting date or datetime value. expr is an expression specifying\nthe interval value to be added or subtracted from the starting date.\nexpr is a string; it may start with a "-" for negative intervals. unit\nis a keyword indicating the units in which the expression should be\ninterpreted.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [DATE_FORMAT] declaration=date,format category=Date and Time Functions -description=Formats the date value according to the format string +description=Formats the date value according to the format string.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [DATE_SUB] declaration=date,INTERVAL expr unit category=Date and Time Functions -description=See the description for DATE_ADD() +description=See the description for DATE_ADD().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [DAY] declaration=date category=Date and Time Functions -description=DAY() is a synonym for DAYOFMONTH() +description=DAY() is a synonym for DAYOFMONTH().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [DAYNAME] declaration=date category=Date and Time Functions -description=Returns the name of the weekday for date +description=Returns the name of the weekday for date. The language used for the\nname is controlled by the value of the lc_time_names system variable\n(http://dev.mysql.com/doc/refman/5.7/en/locale-support.html).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [DAYOFMONTH] declaration=date category=Date and Time Functions -description=Returns the day of the month for date, in the range 1 to 31, or 0 for dates\nsuch as '0000-00-00' or '2008-00-00' that have a zero day part +description=Returns the day of the month for date, in the range 1 to 31, or 0 for\ndates such as '0000-00-00' or '2008-00-00' that have a zero day part.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [DAYOFWEEK] declaration=date category=Date and Time Functions -description=Returns the weekday index for date (1 = Sunday, 2 = Monday, +description=Returns the weekday index for date (1 = Sunday, 2 = Monday, ..., 7 =\nSaturday). These index values correspond to the ODBC standard.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [DAYOFYEAR] declaration=date category=Date and Time Functions -description=Returns the day of the year for date, in the range 1 to 366 +description=Returns the day of the year for date, in the range 1 to 366.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[DEC] +declaration=M[,D] +category=Data Types +description=[ZEROFILL], FIXED[(M[,D])] [UNSIGNED] [ZEROFILL]\n\nThese types are synonyms for DECIMAL. The FIXED synonym is available\nfor compatibility with other database systems.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html +[DECIMAL] +declaration=M[,D] +category=Data Types +description=A packed "exact" fixed-point number. M is the total number of digits\n(the precision) and D is the number of digits after the decimal point\n(the scale). The decimal point and (for negative numbers) the "-" sign\nare not counted in M. If D is 0, values have no decimal point or\nfractional part. The maximum number of digits (M) for DECIMAL is 65.\nThe maximum number of supported decimals (D) is 30. If D is omitted,\nthe default is 0. If M is omitted, the default is 10.\n\nUNSIGNED, if specified, disallows negative values.\n\nAll basic calculations (+, -, *, /) with DECIMAL columns are done with\na precision of 65 digits.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html +[DECODE] +declaration=crypt_str,pass_str +category=Encryption Functions +description=DECODE() decrypts the encrypted string crypt_str using pass_str as the\npassword. crypt_str should be a string returned from ENCODE().\n\n*Note*: The ENCODE() and DECODE() functions are deprecated in MySQL\n5.7, will be removed in a future MySQL release, and should no longer be\nused. Consider using AES_ENCRYPT() and AES_DECRYPT() instead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html [DEFAULT] declaration=col_name category=Miscellaneous Functions -description=Returns the default value for a table column +description=Returns the default value for a table column. An error results if the\ncolumn has no default value.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [DEGREES] declaration=X category=Numeric Functions -description=Returns the argument X, converted from radians to degrees +description=Returns the argument X, converted from radians to degrees.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html +[DES_DECRYPT] +declaration=crypt_str[,key_str] +category=Encryption Functions +description=Decrypts a string encrypted with DES_ENCRYPT(). If an error occurs,\nthis function returns NULL.\n\nThis function works only if MySQL has been configured with SSL support.\nSee http://dev.mysql.com/doc/refman/5.7/en/ssl-connections.html.\n\nIf no key_str argument is given, DES_DECRYPT() examines the first byte\nof the encrypted string to determine the DES key number that was used\nto encrypt the original string, and then reads the key from the DES key\nfile to decrypt the message. For this to work, the user must have the\nSUPER privilege. The key file can be specified with the --des-key-file\nserver option.\n\nIf you pass this function a key_str argument, that string is used as\nthe key for decrypting the message.\n\nIf the crypt_str argument does not appear to be an encrypted string,\nMySQL returns the given crypt_str.\n\n*Note*: The DES_ENCRYPT() and DES_DECRYPT() functions are deprecated as\nof MySQL 5.7.6, will be removed in a future MySQL release, and should\nno longer be used. Consider using AES_ENCRYPT() and AES_DECRYPT()\ninstead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html +[DES_ENCRYPT] +declaration=str[,{key_num|key_str}] +category=Encryption Functions +description=Encrypts the string with the given key using the Triple-DES algorithm.\n\nThis function works only if MySQL has been configured with SSL support.\nSee http://dev.mysql.com/doc/refman/5.7/en/ssl-connections.html.\n\nThe encryption key to use is chosen based on the second argument to\nDES_ENCRYPT(), if one was given. With no argument, the first key from\nthe DES key file is used. With a key_num argument, the given key number\n(0 to 9) from the DES key file is used. With a key_str argument, the\ngiven key string is used to encrypt str.\n\nThe key file can be specified with the --des-key-file server option.\n\nThe return string is a binary string where the first character is\nCHAR(128 | key_num). If an error occurs, DES_ENCRYPT() returns NULL.\n\nThe 128 is added to make it easier to recognize an encrypted key. If\nyou use a string key, key_num is 127.\n\nThe string length for the result is given by this formula:\n\nnew_len = orig_len + (8 - (orig_len % 8)) + 1\n\nEach line in the DES key file has the following format:\n\nkey_num des_key_str\n\nEach key_num value must be a number in the range from 0 to 9. Lines in\nthe file may be in any order. des_key_str is the string that is used to\nencrypt the message. There should be at least one space between the\nnumber and the key. The first key is the default key that is used if\nyou do not specify any key argument to DES_ENCRYPT().\n\nYou can tell MySQL to read new key values from the key file with the\nFLUSH DES_KEY_FILE statement. This requires the RELOAD privilege.\n\nOne benefit of having a set of default keys is that it gives\napplications a way to check for the existence of encrypted column\nvalues, without giving the end user the right to decrypt those values.\n\n*Note*: The DES_ENCRYPT() and DES_DECRYPT() functions are deprecated as\nof MySQL 5.7.6, will be removed in a future MySQL release, and should\nno longer be used. Consider using AES_ENCRYPT() and AES_DECRYPT()\ninstead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html +[DIMENSION] +declaration=g +category=Geometry properties +description=Returns the inherent dimension of the geometry value g. The result can\nbe -1, 0, 1, or 2. The meaning of these values is given in\nhttp://dev.mysql.com/doc/refman/5.7/en/gis-class-geometry.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-general-property-functions.html +[DISJOINT] +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 is spatially disjoint from (does\nnot intersect) g2.\n\nThis function is deprecated as of MySQL 5.7.6 and will be removed in a\nfuture MySQL release. Use MBRDisjoint() instead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html +[DISTANCE] +declaration=g1,g2 +category=Geometry relations +description=ST_Distance() and Distance() are synonyms. For more information, see\nthe description of ST_Distance().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html +[DOUBLE] +declaration=M,D +category=Data Types +description=A normal-size (double-precision) floating-point number. Permissible\nvalues are -1.7976931348623157E+308 to -2.2250738585072014E-308, 0, and\n2.2250738585072014E-308 to 1.7976931348623157E+308. These are the\ntheoretical limits, based on the IEEE standard. The actual range might\nbe slightly smaller depending on your hardware or operating system.\n\nM is the total number of digits and D is the number of digits following\nthe decimal point. If M and D are omitted, values are stored to the\nlimits permitted by the hardware. A double-precision floating-point\nnumber is accurate to approximately 15 decimal places.\n\nUNSIGNED, if specified, disallows negative values.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html [ELT] declaration=N,str1,str2,str3,... category=String Functions -description=ELT() returns the Nth element of the list of strings: str1 if N = 1, str2\nif N = 2, and so on +description=ELT() returns the Nth element of the list of strings: str1 if N = 1,\nstr2 if N = 2, and so on. Returns NULL if N is less than 1 or greater\nthan the number of arguments. ELT() is the complement of FIELD().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[ENCODE] +declaration=str,pass_str +category=Encryption Functions +description=ENCODE() encrypts str using pass_str as the password. The result is a\nbinary string of the same length as str. To decrypt the result, use\nDECODE().\n\n*Note*: The ENCODE() and DECODE() functions are deprecated in MySQL\n5.7, will be removed in a future MySQL release, and should no longer be\nused.\n\nIf you still need to use ENCODE(), a salt value must be used with it to\nreduce risk. For example:\n\nENCODE('plaintext', CONCAT('my_random_salt','my_secret_password'))\n\nA new random salt value must be used whenever a password is updated.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html +[ENCRYPT] +declaration=str[,salt] +category=Encryption Functions +description=Encrypts str using the Unix crypt() system call and returns a binary\nstring. The salt argument must be a string with at least two characters\nor the result will be NULL. If no salt argument is given, a random\nvalue is used.\n\n*Note*: The ENCRYPT() function is deprecated as of MySQL 5.7.6, will be\nremoved in a future MySQL release, and should no longer be used.\nConsider using AES_ENCRYPT() instead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html +[ENDPOINT] +declaration=ls +category=LineString properties +description=Returns the Point that is the endpoint of the LineString value ls.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-linestring-property-functions.html [ENUM] declaration='value1','value2',... category=Data Types -description=collation_name] An enumeration +description=collation_name]\n\nAn enumeration. A string object that can have only one value, chosen\nfrom the list of values 'value1', 'value2', ..., NULL or the special ''\nerror value. ENUM values are represented internally as integers.\n\nAn ENUM column can have a maximum of 65,535 distinct elements. (The\npractical limit is less than 3000.) A table can have no more than 255\nunique element list definitions among its ENUM and SET columns\nconsidered as a group. For more information on these limits, see\nhttp://dev.mysql.com/doc/refman/5.7/en/limits-frm-file.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html +[ENVELOPE] +declaration=g +category=Geometry properties +description=ST_Envelope() and Envelope() are synonyms. For more information, see\nthe description of ST_Envelope().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-general-property-functions.html +[EQUALS] +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 is spatially equal to g2.\n\nThis function is deprecated as of MySQL 5.7.6 and will be removed in a\nfuture MySQL release. Use MBREquals() instead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html [EXP] declaration=X category=Numeric Functions -description=Returns the value of e (the base of natural logarithms) raised to the power\nof X +description=Returns the value of e (the base of natural logarithms) raised to the\npower of X. The inverse of this function is LOG() (using a single\nargument only) or LN().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [EXPORT_SET] declaration=bits,on,off[,separator[,number_of_bits]] category=String Functions -description=Returns a string such that for every bit set in the value bits, you get an\non string and for every bit not set in the value, you get an off string +description=Returns a string such that for every bit set in the value bits, you get\nan on string and for every bit not set in the value, you get an off\nstring. Bits in bits are examined from right to left (from low-order to\nhigh-order bits). Strings are added to the result from left to right,\nseparated by the separator string (the default being the comma\ncharacter ","). The number of bits examined is given by number_of_bits,\nwhich has a default of 64 if not specified. number_of_bits is silently\nclipped to 64 if larger than 64. It is treated as an unsigned integer,\nso a value of -1 is effectively the same as 64.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[EXTERIORRING] +declaration=poly +category=Polygon properties +description=Returns the exterior ring of the Polygon value poly as a LineString.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-polygon-property-functions.html [EXTRACT] declaration=unit FROM date category=Date and Time Functions -description=The EXTRACT() function uses the same kinds of unit specifiers as DATE_ADD()\nor DATE_SUB(), but extracts parts from the date rather than performing date\narithmetic +description=The EXTRACT() function uses the same kinds of unit specifiers as\nDATE_ADD() or DATE_SUB(), but extracts parts from the date rather than\nperforming date arithmetic.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [EXTRACTVALUE] declaration=xml_frag, xpath_expr -category=XML -description=ExtractValue() takes two string arguments, a fragment of XML markup\nxml_frag and an XPath expression xpath_expr (also known as a locator); it\nreturns the text (CDATA) of the first text node which is a child of the\nelement or elements matched by the XPath expression +category=String Functions +description=ExtractValue() takes two string arguments, a fragment of XML markup\nxml_frag and an XPath expression xpath_expr (also known as a locator);\nit returns the text (CDATA) of the first text node which is a child of\nthe elements or elements matched by the XPath expression.\n\nUsing this function is the equivalent of performing a match using the\nxpath_expr after appending /text(). In other words,\nExtractValue('Sakila', '/a/b') and\nExtractValue('Sakila', '/a/b/text()') produce the same\nresult.\n\nIf multiple matches are found, the content of the first child text node\nof each matching element is returned (in the order matched) as a\nsingle, space-delimited string.\n\nIf no matching text node is found for the expression (including the\nimplicit /text())---for whatever reason, as long as xpath_expr is\nvalid, and xml_frag consists of elements which are properly nested and\nclosed---an empty string is returned. No distinction is made between a\nmatch on an empty element and no match at all. This is by design.\n\nIf you need to determine whether no matching element was found in\nxml_frag or such an element was found but contained no child text\nnodes, you should test the result of an expression that uses the XPath\ncount() function. For example, both of these statements return an empty\nstring, as shown here:\n\nmysql> SELECT ExtractValue('', '/a/b');\n+-------------------------------------+\n| ExtractValue('', '/a/b') |\n+-------------------------------------+\n| |\n+-------------------------------------+\n1 row in set (0.00 sec)\n\nmysql> SELECT ExtractValue('', '/a/b');\n+-------------------------------------+\n| ExtractValue('', '/a/b') |\n+-------------------------------------+\n| |\n+-------------------------------------+\n1 row in set (0.00 sec)\n\nHowever, you can determine whether there was actually a matching\nelement using the following:\n\nmysql> SELECT ExtractValue('', 'count(/a/b)');\n+-------------------------------------+\n| ExtractValue('', 'count(/a/b)') |\n+-------------------------------------+\n ... [FIELD] declaration=str,str1,str2,str3,... category=String Functions -description=Returns the index (position) of str in the str1, str2, str3, +description=Returns the index (position) of str in the str1, str2, str3, ... list.\nReturns 0 if str is not found.\n\nIf all arguments to FIELD() are strings, all arguments are compared as\nstrings. If all arguments are numbers, they are compared as numbers.\nOtherwise, the arguments are compared as double.\n\nIf str is NULL, the return value is 0 because NULL fails equality\ncomparison with any value. FIELD() is the complement of ELT().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [FIND_IN_SET] declaration=str,strlist category=String Functions -description=Returns a value in the range of 1 to N if the string str is in the string\nlist strlist consisting of N substrings -[FIRST_VALUE] -declaration=expr -category=Window Functions -description=Returns the value of expr from the first row of the window frame +description=Returns a value in the range of 1 to N if the string str is in the\nstring list strlist consisting of N substrings. A string list is a\nstring composed of substrings separated by "," characters. If the first\nargument is a constant string and the second is a column of type SET,\nthe FIND_IN_SET() function is optimized to use bit arithmetic. Returns\n0 if str is not in strlist or if strlist is the empty string. Returns\nNULL if either argument is NULL. This function does not work properly\nif the first argument contains a comma (",") character.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[FLOAT] +declaration=M,D +category=Data Types +description=A small (single-precision) floating-point number. Permissible values\nare -3.402823466E+38 to -1.175494351E-38, 0, and 1.175494351E-38 to\n3.402823466E+38. These are the theoretical limits, based on the IEEE\nstandard. The actual range might be slightly smaller depending on your\nhardware or operating system.\n\nM is the total number of digits and D is the number of digits following\nthe decimal point. If M and D are omitted, values are stored to the\nlimits permitted by the hardware. A single-precision floating-point\nnumber is accurate to approximately 7 decimal places.\n\nUNSIGNED, if specified, disallows negative values.\n\nUsing FLOAT might give you some unexpected problems because all\ncalculations in MySQL are done with double precision. See\nhttp://dev.mysql.com/doc/refman/5.7/en/no-matching-rows.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html [FLOOR] declaration=X category=Numeric Functions -description=Returns the largest integer value not greater than X +description=Returns the largest integer value not greater than X.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [FORMAT] declaration=X,D[,locale] category=String Functions -description=Formats the number X to a format like '#,###,### -[FORMAT_BYTES] -declaration=count -category=Performance Schema Functions -description=Given a numeric byte count, converts it to human-readable format and\nreturns a string consisting of a value and a units indicator -[FORMAT_PICO_TIME] -declaration=time_val -category=Performance Schema Functions -description=Given a numeric Performance Schema latency or wait time in picoseconds,\nconverts it to human-readable format and returns a string consisting of a\nvalue and a units indicator -[FROM_BASE64] -declaration=str -category=String Functions -description=Takes a string encoded with the base-64 encoded rules used by TO_BASE64()\nand returns the decoded result as a binary string +description=Formats the number X to a format like '#,###,###.##', rounded to D\ndecimal places, and returns the result as a string. If D is 0, the\nresult has no decimal point or fractional part.\n\nThe optional third parameter enables a locale to be specified to be\nused for the result number's decimal point, thousands separator, and\ngrouping between separators. Permissible locale values are the same as\nthe legal values for the lc_time_names system variable (see\nhttp://dev.mysql.com/doc/refman/5.7/en/locale-support.html). If no\nlocale is specified, the default is 'en_US'.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[FOUND_ROWS] +declaration= +category=Information Functions +description=A SELECT statement may include a LIMIT clause to restrict the number of\nrows the server returns to the client. In some cases, it is desirable\nto know how many rows the statement would have returned without the\nLIMIT, but without running the statement again. To obtain this row\ncount, include a SQL_CALC_FOUND_ROWS option in the SELECT statement,\nand then invoke FOUND_ROWS() afterward:\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [FROM_DAYS] declaration=N category=Date and Time Functions -description=Given a day number N, returns a DATE value +description=Given a day number N, returns a DATE value.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [FROM_UNIXTIME] -declaration=unix_timestamp[,format] +declaration=unix_timestamp category=Date and Time Functions -description=Returns a representation of the unix_timestamp argument as a value in\n'YYYY-MM-DD hh:mm:ss' or YYYYMMDDhhmmss format, depending on whether the\nfunction is used in a string or numeric context -[GEOMCOLLECTION] -declaration=g [, g] ... -category=Geometry Constructors -description=Constructs a GeomCollection value from the geometry arguments +description=Returns a representation of the unix_timestamp argument as a value in\n'YYYY-MM-DD HH:MM:SS' or YYYYMMDDHHMMSS format, depending on whether\nthe function is used in a string or numeric context. The value is\nexpressed in the current time zone. unix_timestamp is an internal\ntimestamp value such as is produced by the UNIX_TIMESTAMP() function.\n\nIf format is given, the result is formatted according to the format\nstring, which is used the same way as listed in the entry for the\nDATE_FORMAT() function.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[GEOMCOLLFROMTEXT] +declaration=wkt[,srid] +category=WKT +description=Constructs a GeometryCollection value using its WKT representation and\nSRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkt-functions.html +[GEOMCOLLFROMWKB] +declaration=wkb[,srid] +category=WKB +description=Constructs a GeometryCollection value using its WKB representation and\nSRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkb-functions.html [GEOMETRYCOLLECTION] -declaration=g [, g] ... -category=Geometry Constructors -description=Constructs a GeomCollection value from the geometry arguments +declaration=g1,g2,... +category=Geometry constructors +description=Constructs a GeometryCollection.\n\nAs of MySQL 5.7.5, GeometryCollection() returns all the proper\ngeometries contained in the argument even if a nonsupported geometry is\npresent. Before 5.7.5, if the argument contains a nonsupported\ngeometry, the return value is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-mysql-specific-functions.html +[GEOMETRYN] +declaration=gc,N +category=GeometryCollection properties +description=Returns the N-th geometry in the GeometryCollection value gc.\nGeometries are numbered beginning with 1.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-geometrycollection-property-functions.html +[GEOMETRYTYPE] +declaration=g +category=Geometry properties +description=Returns a binary string indicating the name of the geometry type of\nwhich the geometry instance g is a member. The name corresponds to one\nof the instantiable Geometry subclasses.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-general-property-functions.html +[GEOMFROMTEXT] +declaration=wkt[,srid] +category=WKT +description=Constructs a geometry value of any type using its WKT representation\nand SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkt-functions.html +[GEOMFROMWKB] +declaration=wkb[,srid] +category=WKB +description=Constructs a geometry value of any type using its WKB representation\nand SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkb-functions.html [GET_FORMAT] declaration={DATE|TIME|DATETIME}, {'EUR'|'USA'|'JIS'|'ISO'|'INTERNAL'} category=Date and Time Functions -description=Returns a format string +description=Returns a format string. This function is useful in combination with\nthe DATE_FORMAT() and the STR_TO_DATE() functions.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [GET_LOCK] declaration=str,timeout -category=Locking Functions -description=Tries to obtain a lock with a name given by the string str, using a timeout\nof timeout seconds +category=Miscellaneous Functions +description=Tries to obtain a lock with a name given by the string str, using a\ntimeout of timeout seconds. A negative timeout value means infinite\ntimeout.\n\nReturns 1 if the lock was obtained successfully, 0 if the attempt timed\nout (for example, because another client has previously locked the\nname), or NULL if an error occurred (such as running out of memory or\nthe thread was killed with mysqladmin kill). If you have a lock\nobtained with GET_LOCK(), it is released when you execute\nRELEASE_LOCK() or your connection terminates (either normally or\nabnormally). Lock release may also occur with another call to\nGET_LOCK():\n\no Before 5.7.5, only a single simultaneous lock can be acquired and\n GET_LOCK() releases any existing lock.\n\no In MySQL 5.7.5, GET_LOCK() was reimplemented using the metadata\n locking (MDL) subsystem and its capabilities were extended. Multiple\n simultaneous locks can be acquired and GET_LOCK() does not release\n any existing locks. It is even possible for a given session to\n acquire multiple locks for the same name. Other sessions cannot\n acquire a lock with that name until the acquiring session releases\n all its locks for the name.\n\n As a result of the MDL reimplementation, locks acquired with\n GET_LOCK() appear in the Performance Schema metadata_locks table. The\n OBJECT_TYPE column says USER LEVEL LOCK and the OBJECT_NAME column\n indicates the lock name. Also, the capability of acquiring multiple\n locks introduces the possibility of deadlock among clients. An\n ER_USER_LOCK_DEADLOCK error is returned when this occurs.\n\nThe difference in lock acquisition behavior as of MySQL 5.7.5 can be\nseen by the following example. Suppose that you execute these\nstatements:\n\nSELECT GET_LOCK('lock1',10);\nSELECT GET_LOCK('lock2',10);\nSELECT RELEASE_LOCK('lock2');\nSELECT RELEASE_LOCK('lock1');\n\nIn MySQL 5.7.5 or later, the second GET_LOCK() acquires a second lock\nand both RELEASE_LOCK() calls return 1 (success). Before MySQL 5.7.5,\nthe second GET_LOCK() releases the first lock ('lock1') and the second\nRELEASE_LOCK() returns NULL (failure) because there is no 'lock1' to\nrelease.\n\nMySQL 5.7.5 and later enforces a maximum length on lock names of 64\ncharacters. Previously, no limit was enforced.\n\nLocks obtained with GET_LOCK() do not interact with transactions. That\n ... +[GLENGTH] +declaration=ls +category=LineString properties +description=Returns a double-precision number indicating the length of the\nLineString value ls in its associated spatial reference.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-linestring-property-functions.html [GREATEST] declaration=value1,value2,... -category=Comparison Operators -description=With two or more arguments, returns the largest (maximum-valued) argument -[GROUPING] -declaration=expr [, expr] ... -category=Miscellaneous Functions -description=For GROUP BY queries that include a WITH ROLLUP modifier, the ROLLUP\noperation produces super-aggregate output rows where NULL represents the\nset of all values +category=Comparison operators +description=With two or more arguments, returns the largest (maximum-valued)\nargument. The arguments are compared using the same rules as for\nLEAST().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html [GROUP_CONCAT] declaration=expr -category=Aggregate Functions and Modifiers -description=This function returns a string result with the concatenated non-NULL values\nfrom a group +category=Functions and Modifiers for Use with GROUP BY +description=This function returns a string result with the concatenated non-NULL\nvalues from a group. It returns NULL if there are no non-NULL values.\nThe full syntax is as follows:\n\nGROUP_CONCAT([DISTINCT] expr [,expr ...]\n [ORDER BY {unsigned_integer | col_name | expr}\n [ASC | DESC] [,col_name ...]]\n [SEPARATOR str_val])\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [GTID_SUBSET] -declaration=set1,set2 -category=GTID -description=Given two sets of global transaction identifiers set1 and set2, returns\ntrue if all GTIDs in set1 are also in set2 +declaration=subset,set +category=MBR +description=Given two sets of global transaction IDs subset and set, returns true\nif all GTIDs in subset are also in set. Returns false otherwise.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gtid-functions.html [GTID_SUBTRACT] -declaration=set1,set2 -category=GTID -description=Given two sets of global transaction identifiers set1 and set2, returns\nonly those GTIDs from set1 that are not in set2 +declaration=set,subset +category=MBR +description=Given two sets of global transaction IDs subset and set, returns only\nthose GTIDs from set that are not in subset.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gtid-functions.html [HEX] declaration=str category=String Functions -description=For a string argument str, HEX() returns a hexadecimal string\nrepresentation of str where each byte of each character in str is converted\nto two hexadecimal digits +description=For a string argument str, HEX() returns a hexadecimal string\nrepresentation of str where each byte of each character in str is\nconverted to two hexadecimal digits. (Multibyte characters therefore\nbecome more than two digits.) The inverse of this operation is\nperformed by the UNHEX() function.\n\nFor a numeric argument N, HEX() returns a hexadecimal string\nrepresentation of the value of N treated as a longlong (BIGINT) number.\nThis is equivalent to CONV(N,10,16). The inverse of this operation is\nperformed by CONV(HEX(N),16,10).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [HOUR] declaration=time category=Date and Time Functions -description=Returns the hour for time +description=Returns the hour for time. The range of the return value is 0 to 23 for\ntime-of-day values. However, the range of TIME values actually is much\nlarger, so HOUR can return values greater than 23.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [IFNULL] declaration=expr1,expr2 -category=Flow Control Functions -description=If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns expr2 +category=Control flow functions +description=If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns\nexpr2. IFNULL() returns a numeric or string value, depending on the\ncontext in which it is used.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/control-flow-functions.html +[IN] +declaration=value,... +category=Comparison operators +description=Returns 1 if expr is equal to any of the values in the IN list, else\nreturns 0. If all values are constants, they are evaluated according to\nthe type of expr and sorted. The search for the item then is done using\na binary search. This means IN is very quick if the IN value list\nconsists entirely of constants. Otherwise, type conversion takes place\naccording to the rules described in\nhttp://dev.mysql.com/doc/refman/5.7/en/type-conversion.html, but\napplied to all the arguments.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html [INET6_ATON] declaration=expr category=Miscellaneous Functions -description=Given an IPv6 or IPv4 network address as a string, returns a binary string\nthat represents the numeric value of the address in network byte order (big\nendian) +description=Given an IPv6 or IPv4 network address as a string, returns a binary\nstring that represents the numeric value of the address in network byte\norder (big endian). Because numeric-format IPv6 addresses require more\nbytes than the largest integer type, the representation returned by\nthis function has the VARBINARY data type: VARBINARY(16) for IPv6\naddresses and VARBINARY(4) for IPv4 addresses. If the argument is not a\nvalid address, INET6_ATON() returns NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [INET6_NTOA] declaration=expr category=Miscellaneous Functions -description=Given an IPv6 or IPv4 network address represented in numeric form as a\nbinary string, returns the string representation of the address as a string\nin the connection character set +description=Given an IPv6 or IPv4 network address represented in numeric form as a\nbinary string, returns the string representation of the address as a\nnonbinary string in the connection character set. If the argument is\nnot a valid address, INET6_NTOA() returns NULL.\n\nINET6_NTOA() has these properties:\n\no It does not use operating system functions to perform conversions,\n thus the output string is platform independent.\n\no The return string has a maximum length of 39 (4 x 8 + 7). Given this\n statement:\n\nCREATE TABLE t AS SELECT INET6_NTOA(expr) AS c1;\n\n The resulting table would have this definition:\n\nCREATE TABLE t (c1 VARCHAR(39) CHARACTER SET utf8 DEFAULT NULL);\n\no The return string uses lowercase letters for IPv6 addresses.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [INET_ATON] declaration=expr category=Miscellaneous Functions -description=Given the dotted-quad representation of an IPv4 network address as a\nstring, returns an integer that represents the numeric value of the address\nin network byte order (big endian) +description=Given the dotted-quad representation of an IPv4 network address as a\nstring, returns an integer that represents the numeric value of the\naddress in network byte order (big endian). INET_ATON() returns NULL if\nit does not understand its argument.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [INET_NTOA] declaration=expr category=Miscellaneous Functions -description=Given a numeric IPv4 network address in network byte order, returns the\ndotted-quad string representation of the address as a string in the\nconnection character set +description=Given a numeric IPv4 network address in network byte order, returns the\ndotted-quad string representation of the address as a nonbinary string\nin the connection character set. INET_NTOA() returns NULL if it does\nnot understand its argument.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [INSTR] declaration=str,substr category=String Functions -description=Returns the position of the first occurrence of substring substr in string\nstr +description=Returns the position of the first occurrence of substring substr in\nstring str. This is the same as the two-argument form of LOCATE(),\nexcept that the order of the arguments is reversed.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[INT] +declaration=M +category=Data Types +description=A normal-size integer. The signed range is -2147483648 to 2147483647.\nThe unsigned range is 0 to 4294967295.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html +[INTEGER] +declaration=M +category=Data Types +description=This type is a synonym for INT.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html +[INTERIORRINGN] +declaration=poly,N +category=Polygon properties +description=Returns the N-th interior ring for the Polygon value poly as a\nLineString. Rings are numbered beginning with 1.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-polygon-property-functions.html +[INTERSECTS] +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 spatially intersects g2.\n\nThis function is deprecated as of MySQL 5.7.6 and will be removed in a\nfuture MySQL release. Use MBRIntersects() instead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html [INTERVAL] declaration=N,N1,N2,N3,... -category=Comparison Operators -description=Returns 0 if N < N1, 1 if N < N2 and so on or -1 if N is NULL +category=Comparison operators +description=Returns 0 if N < N1, 1 if N < N2 and so on or -1 if N is NULL. All\narguments are treated as integers. It is required that N1 < N2 < N3 <\n... < Nn for this function to work correctly. This is because a binary\nsearch is used (very fast).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html +[ISCLOSED] +declaration=ls +category=LineString properties +description=Returns 1 if the LineString value ls is closed (that is, its\nStartPoint() and EndPoint() values are the same) and is simple (does\nnot pass through the same point more than once). Returns 0 if ls is not\nclosed, and -1 if it is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-linestring-property-functions.html +[ISEMPTY] +declaration=g +category=Geometry properties +description=This function is a placeholder that returns 0 for any valid geometry\nvalue, 1 for any invalid geometry value or NULL.\n\nMySQL does not support GIS EMPTY values such as POINT EMPTY.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-general-property-functions.html [ISNULL] declaration=expr -category=Comparison Operators -description=If expr is NULL, ISNULL() returns 1, otherwise it returns 0 +category=Comparison operators +description=If expr is NULL, ISNULL() returns 1, otherwise it returns 0.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html +[ISSIMPLE] +declaration=g +category=Geometry properties +description=Returns 1 if the geometry value g has no anomalous geometric points,\nsuch as self-intersection or self-tangency. IsSimple() returns 0 if the\nargument is not simple, and NULL if it is NULL.\n\nThe description of each instantiable geometric class given earlier in\nthe chapter includes the specific conditions that cause an instance of\nthat class to be classified as not simple. (See [HELP Geometry\nhierarchy].)\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-general-property-functions.html [IS_FREE_LOCK] declaration=str -category=Locking Functions -description=Checks whether the lock named str is free to use (that is, not locked) +category=Miscellaneous Functions +description=Checks whether the lock named str is free to use (that is, not locked).\nReturns 1 if the lock is free (no one is using the lock), 0 if the lock\nis in use, and NULL if an error occurs (such as an incorrect argument).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [IS_IPV4] declaration=expr category=Miscellaneous Functions -description=Returns 1 if the argument is a valid IPv4 address specified as a string, 0\notherwise +description=Returns 1 if the argument is a valid IPv4 address specified as a\nstring, 0 otherwise.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [IS_IPV4_COMPAT] declaration=expr category=Miscellaneous Functions -description=This function takes an IPv6 address represented in numeric form as a binary\nstring, as returned by INET6_ATON() +description=This function takes an IPv6 address represented in numeric form as a\nbinary string, as returned by INET6_ATON(). It returns 1 if the\nargument is a valid IPv4-compatible IPv6 address, 0 otherwise.\nIPv4-compatible addresses have the form ::ipv4_address.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [IS_IPV4_MAPPED] declaration=expr category=Miscellaneous Functions -description=This function takes an IPv6 address represented in numeric form as a binary\nstring, as returned by INET6_ATON() +description=This function takes an IPv6 address represented in numeric form as a\nbinary string, as returned by INET6_ATON(). It returns 1 if the\nargument is a valid IPv4-mapped IPv6 address, 0 otherwise. IPv4-mapped\naddresses have the form ::ffff:ipv4_address.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [IS_IPV6] declaration=expr category=Miscellaneous Functions -description=Returns 1 if the argument is a valid IPv6 address specified as a string, 0\notherwise +description=Returns 1 if the argument is a valid IPv6 address specified as a\nstring, 0 otherwise. This function does not consider IPv4 addresses to\nbe valid IPv6 addresses.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [IS_USED_LOCK] declaration=str -category=Locking Functions -description=Checks whether the lock named str is in use (that is, locked) -[IS_UUID] -declaration=string_uuid category=Miscellaneous Functions -description=Returns 1 if the argument is a valid string-format UUID, 0 if the argument\nis not a valid UUID, and NULL if the argument is NULL -[JSON_ARRAY] -declaration=[val[, val] ...] -category=MBR Functions -description=Evaluates a (possibly empty) list of values and returns a JSON array\ncontaining those values -[JSON_ARRAYAGG] -declaration=col_or_expr -category=Aggregate Functions and Modifiers -description=Aggregates a result set as a single JSON array whose elements consist of\nthe rows -[JSON_ARRAY_APPEND] -declaration=json_doc, path, val[, path, val] ... -category=MBR Functions -description=Appends values to the end of the indicated arrays within a JSON document\nand returns the result -[JSON_ARRAY_INSERT] -declaration=json_doc, path, val[, path, val] ... -category=MBR Functions -description=Updates a JSON document, inserting into an array within the document and\nreturning the modified document -[JSON_CONTAINS] -declaration=target, candidate[, path] -category=MBR Functions -description=Indicates by returning 1 or 0 whether a given candidate JSON document is\ncontained within a target JSON document, or---if a path argument was\nsupplied---whether the candidate is found at a specific path within the\ntarget -[JSON_CONTAINS_PATH] -declaration=json_doc, one_or_all, path[, path] ... -category=MBR Functions -description=Returns 0 or 1 to indicate whether a JSON document contains data at a given\npath or paths -[JSON_DEPTH] -declaration=json_doc -category=MBR Functions -description=Returns the maximum depth of a JSON document -[JSON_EXTRACT] -declaration=json_doc, path[, path] ... -category=MBR Functions -description=Returns data from a JSON document, selected from the parts of the document\nmatched by the path arguments -[JSON_INSERT] -declaration=json_doc, path, val[, path, val] ... -category=MBR Functions -description=Inserts data into a JSON document and returns the result -[JSON_KEYS] -declaration=json_doc[, path] -category=MBR Functions -description=Returns the keys from the top-level value of a JSON object as a JSON array,\nor, if a path argument is given, the top-level keys from the selected path -[JSON_LENGTH] -declaration=json_doc[, path] -category=MBR Functions -description=Returns the length of a JSON document, or, if a path argument is given, the\nlength of the value within the document identified by the path -[JSON_MERGE] -declaration=json_doc, json_doc[, json_doc] ... -category=MBR Functions -description=Merges two or more JSON documents -[JSON_OBJECT] -declaration=[key, val[, key, val] ...] -category=MBR Functions -description=Evaluates a (possibly empty) list of key-value pairs and returns a JSON\nobject containing those pairs -[JSON_OBJECTAGG] -declaration=key, value -category=Aggregate Functions and Modifiers -description=Takes two column names or expressions as arguments, the first of these\nbeing used as a key and the second as a value, and returns a JSON object\ncontaining key-value pairs -[JSON_OVERLAPS] -declaration=json_doc1, json_doc2 -category=MBR Functions -description=Compares two JSON documents -[JSON_PRETTY] -declaration=json_val -category=MBR Functions -description=Provides pretty-printing of JSON values similar to that implemented in PHP\nand by other languages and database systems -[JSON_QUOTE] -declaration=string -category=MBR Functions -description=Quotes a string as a JSON value by wrapping it with double quote characters\nand escaping interior quote and other characters, then returning the result\nas a utf8mb4 string -[JSON_REMOVE] -declaration=json_doc, path[, path] ... -category=MBR Functions -description=Removes data from a JSON document and returns the result -[JSON_REPLACE] -declaration=json_doc, path, val[, path, val] ... -category=MBR Functions -description=Replaces existing values in a JSON document and returns the result -[JSON_SCHEMA_VALID] -declaration=schema,document -category=MBR Functions -description=Validates a JSON document against a JSON schema -[JSON_SCHEMA_VALIDATION_REPORT] -declaration=schema,document -category=MBR Functions -description=Validates a JSON document against a JSON schema -[JSON_SEARCH] -declaration=json_doc, one_or_all, search_str[, escape_char[, path] ...] -category=MBR Functions -description=Returns the path to the given string within a JSON document -[JSON_SET] -declaration=json_doc, path, val[, path, val] ... -category=MBR Functions -description=Inserts or updates data in a JSON document and returns the result -[JSON_STORAGE_FREE] -declaration=json_val -category=MBR Functions -description=For a JSON column value, this function shows how much storage space was\nfreed in its binary representation after it was updated in place using\nJSON_SET(), JSON_REPLACE(), or JSON_REMOVE() -[JSON_STORAGE_SIZE] -declaration=json_val -category=MBR Functions -description=This function returns the number of bytes used to store the binary\nrepresentation of a JSON document -[JSON_TABLE] -declaration=expr, path COLUMNS (column_list -category=MBR Functions -description=Extracts data from a JSON document and returns it as a relational table\nhaving the specified columns -[JSON_TYPE] -declaration=json_val -category=MBR Functions -description=Returns a utf8mb4 string indicating the type of a JSON value -[JSON_UNQUOTE] -declaration=json_val -category=MBR Functions -description=Unquotes JSON value and returns the result as a utf8mb4 string -[JSON_VALID] -declaration=val -category=MBR Functions -description=Returns 0 or 1 to indicate whether a value is valid JSON -[JSON_VALUE] -declaration=json_doc, path -category=MBR Functions -description=Extracts a value from a JSON document at the path given in the specified\ndocument, and returns the extracted value, optionally converting it to a\ndesired type -[LAG] -declaration=expr [, N[, default]] -category=Window Functions -description=Returns the value of expr from the row that lags (precedes) the current row\nby N rows within its partition +description=Checks whether the lock named str is in use (that is, locked). If so,\nit returns the connection identifier of the client that holds the lock.\nOtherwise, it returns NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html +[JOIN] +declaration=t2, t3, t4 +category=Data Manipulation +description=ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)\n\nis equivalent to:\n\nSELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)\n ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)\n\nIn MySQL, JOIN, CROSS JOIN, and INNER JOIN are syntactic equivalents\n(they can replace each other). In standard SQL, they are not\nequivalent. INNER JOIN is used with an ON clause, CROSS JOIN is used\notherwise.\n\nIn general, parentheses can be ignored in join expressions containing\nonly inner join operations. MySQL also supports nested joins (see\nhttp://dev.mysql.com/doc/refman/5.7/en/nested-join-optimization.html).\n\nIndex hints can be specified to affect how the MySQL optimizer makes\nuse of indexes. For more information, see\nhttp://dev.mysql.com/doc/refman/5.7/en/index-hints.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/join.html [LAST_DAY] declaration=date category=Date and Time Functions -description=Takes a date or datetime value and returns the corresponding value for the\nlast day of the month -[LAST_VALUE] -declaration=expr -category=Window Functions -description=Returns the value of expr from the last row of the window frame +description=Takes a date or datetime value and returns the corresponding value for\nthe last day of the month. Returns NULL if the argument is invalid.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[LAST_INSERT_ID] +declaration= +category=Information Functions +description=With no argument, LAST_INSERT_ID() returns a BIGINT UNSIGNED (64-bit)\nvalue representing the first automatically generated value successfully\ninserted for an AUTO_INCREMENT column as a result of the most recently\nexecuted INSERT statement. The value of LAST_INSERT_ID() remains\nunchanged if no rows are successfully inserted.\n\nWith an argument, LAST_INSERT_ID() returns an unsigned integer.\n\nFor example, after inserting a row that generates an AUTO_INCREMENT\nvalue, you can get the value like this:\n\nmysql> SELECT LAST_INSERT_ID();\n -> 195\n\nThe currently executing statement does not affect the value of\nLAST_INSERT_ID(). Suppose that you generate an AUTO_INCREMENT value\nwith one statement, and then refer to LAST_INSERT_ID() in a\nmultiple-row INSERT statement that inserts rows into a table with its\nown AUTO_INCREMENT column. The value of LAST_INSERT_ID() will remain\nstable in the second statement; its value for the second and later rows\nis not affected by the earlier row insertions. (However, if you mix\nreferences to LAST_INSERT_ID() and LAST_INSERT_ID(expr), the effect is\nundefined.)\n\nIf the previous statement returned an error, the value of\nLAST_INSERT_ID() is undefined. For transactional tables, if the\nstatement is rolled back due to an error, the value of LAST_INSERT_ID()\nis left undefined. For manual ROLLBACK, the value of LAST_INSERT_ID()\nis not restored to that before the transaction; it remains as it was at\nthe point of the ROLLBACK.\n\nPrior to MySQL 5.7.3, this function was not replicated correctly if\nreplication filtering rules were in use. (Bug #17234370, Bug #69861)\n\nWithin the body of a stored routine (procedure or function) or a\ntrigger, the value of LAST_INSERT_ID() changes the same way as for\nstatements executed outside the body of these kinds of objects. The\neffect of a stored routine or trigger upon the value of\nLAST_INSERT_ID() that is seen by following statements depends on the\nkind of routine:\n\no If a stored procedure executes statements that change the value of\n LAST_INSERT_ID(), the changed value is seen by statements that follow\n the procedure call.\n\no For stored functions and triggers that change the value, the value is\n restored when the function or trigger ends, so following statements\n will not see a changed value.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [LCASE] declaration=str category=String Functions -description=LCASE() is a synonym for LOWER() -[LEAD] -declaration=expr [, N[, default]] -category=Window Functions -description=Returns the value of expr from the row that leads (follows) the current row\nby N rows within its partition +description=LCASE() is a synonym for LOWER().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [LEAST] declaration=value1,value2,... -category=Comparison Operators -description=With two or more arguments, returns the smallest (minimum-valued) argument +category=Comparison operators +description=With two or more arguments, returns the smallest (minimum-valued)\nargument. The arguments are compared using the following rules:\n\no If any argument is NULL, the result is NULL. No comparison is needed.\n\no If the return value is used in an INTEGER context or all arguments\n are integer-valued, they are compared as integers.\n\no If the return value is used in a REAL context or all arguments are\n real-valued, they are compared as reals.\n\no If the arguments comprise a mix of numbers and strings, they are\n compared as numbers.\n\no If any argument is a nonbinary (character) string, the arguments are\n compared as nonbinary strings.\n\no In all other cases, the arguments are compared as binary strings.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html [LEFT] declaration=str,len category=String Functions -description=Returns the leftmost len characters from the string str, or NULL if any\nargument is NULL +description=Returns the leftmost len characters from the string str, or NULL if any\nargument is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [LENGTH] declaration=str category=String Functions -description=Returns the length of the string str, measured in bytes +description=Returns the length of the string str, measured in bytes. A multibyte\ncharacter counts as multiple bytes. This means that for a string\ncontaining five 2-byte characters, LENGTH() returns 10, whereas\nCHAR_LENGTH() returns 5.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[LINEFROMTEXT] +declaration=wkt[,srid] +category=WKT +description=Constructs a LineString value using its WKT representation and SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkt-functions.html +[LINEFROMWKB] +declaration=wkb[,srid] +category=WKB +description=Constructs a LineString value using its WKB representation and SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkb-functions.html [LINESTRING] -declaration=pt [, pt] ... -category=Geometry Constructors -description=Constructs a LineString value from a number of Point or WKB Point arguments +declaration=pt1,pt2,... +category=Geometry constructors +description=Constructs a LineString value from a number of Point or WKB Point\narguments. If the number of arguments is less than two, the return\nvalue is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-mysql-specific-functions.html [LN] declaration=X category=Numeric Functions -description=Returns the natural logarithm of X; that is, the base-e logarithm of X +description=Returns the natural logarithm of X; that is, the base-e logarithm of X.\nAs of MySQL 5.7.4, if X is less than or equal to 0.0E0, the error\n"Invalid argument for logarithm" is reported in strict SQL mode, and\nNULL is returned in non-strict mode. Before MySQL 5.7.4, if X is less\nthan or equal to 0.0E0, NULL is returned.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [LOAD_FILE] declaration=file_name category=String Functions -description=Reads the file and returns the file contents as a string +description=Reads the file and returns the file contents as a string. To use this\nfunction, the file must be located on the server host, you must specify\nthe full path name to the file, and you must have the FILE privilege.\nThe file must be readable by all and its size less than\nmax_allowed_packet bytes. If the secure_file_priv system variable is\nset to a nonempty directory name, the file to be loaded must be located\nin that directory.\n\nIf the file does not exist or cannot be read because one of the\npreceding conditions is not satisfied, the function returns NULL.\n\nThe character_set_filesystem system variable controls interpretation of\nfile names that are given as literal strings.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[LOCALTIME] +declaration=[fsp] +category=Date and Time Functions +description=LOCALTIME and LOCALTIME() are synonyms for NOW().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[LOCALTIMESTAMP] +declaration=[fsp] +category=Date and Time Functions +description=LOCALTIMESTAMP and LOCALTIMESTAMP() are synonyms for NOW().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [LOCATE] declaration=substr,str category=String Functions -description=The first syntax returns the position of the first occurrence of substring\nsubstr in string str +description=The first syntax returns the position of the first occurrence of\nsubstring substr in string str. The second syntax returns the position\nof the first occurrence of substring substr in string str, starting at\nposition pos. Returns 0 if substr is not in str.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [LOG] declaration=X category=Numeric Functions -description=If called with one parameter, this function returns the natural logarithm\nof X +description=If called with one parameter, this function returns the natural\nlogarithm of X. As of MySQL 5.7.4, if X is less than or equal to 0.0E0,\nthe error "Invalid argument for logarithm" is reported in strict SQL\nmode, and NULL is returned in non-strict mode. Before MySQL 5.7.4, if X\nis less than or equal to 0.0E0, NULL is returned.\n\nThe inverse of this function (when called with a single argument) is\nthe EXP() function.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [LOG10] declaration=X category=Numeric Functions -description=Returns the base-10 logarithm of X +description=Returns the base-10 logarithm of X. As of MySQL 5.7.4, if X is less\nthan or equal to 0.0E0, the error "Invalid argument for logarithm" is\nreported in strict SQL mode, and NULL is returned in non-strict mode.\nBefore MySQL 5.7.4, if X is less than or equal to 0.0E0, NULL is\nreturned.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [LOG2] declaration=X category=Numeric Functions -description=Returns the base-2 logarithm of X +description=Returns the base-2 logarithm of X. As of MySQL 5.7.4, if X is less than\nor equal to 0.0E0, the error "Invalid argument for logarithm" is\nreported in strict SQL mode, and NULL is returned in non-strict mode.\nBefore MySQL 5.7.4, if X is less than or equal to 0.0E0, NULL is\nreturned.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [LOWER] declaration=str category=String Functions -description=Returns the string str with all characters changed to lowercase according\nto the current character set mapping +description=Returns the string str with all characters changed to lowercase\naccording to the current character set mapping. The default is latin1\n(cp1252 West European).\n\nmysql> SELECT LOWER('QUADRATICALLY');\n -> 'quadratically'\n\nLOWER() (and UPPER()) are ineffective when applied to binary strings\n(BINARY, VARBINARY, BLOB). To perform lettercase conversion, convert\nthe string to a nonbinary string:\n\nmysql> SET @str = BINARY 'New York';\nmysql> SELECT LOWER(@str), LOWER(CONVERT(@str USING latin1));\n+-------------+-----------------------------------+\n| LOWER(@str) | LOWER(CONVERT(@str USING latin1)) |\n+-------------+-----------------------------------+\n| New York | new york |\n+-------------+-----------------------------------+\n\nFor Unicode character sets, LOWER() and UPPER() work accounting to\nUnicode Collation Algorithm (UCA) 5.2.0 for xxx_unicode_520_ci\ncollations and for language-specific collations that are derived from\nthem. For other Unicode collations, LOWER() and UPPER() work accounting\nto Unicode Collation Algorithm (UCA) 4.0.0. See\nhttp://dev.mysql.com/doc/refman/5.7/en/charset-unicode-sets.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [LPAD] declaration=str,len,padstr category=String Functions -description=Returns the string str, left-padded with the string padstr to a length of\nlen characters +description=Returns the string str, left-padded with the string padstr to a length\nof len characters. If str is longer than len, the return value is\nshortened to len characters.\n\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [LTRIM] declaration=str category=String Functions -description=Returns the string str with leading space characters removed +description=Returns the string str with leading space characters removed.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [MAKEDATE] declaration=year,dayofyear category=Date and Time Functions -description=Returns a date, given year and day-of-year values +description=Returns a date, given year and day-of-year values. dayofyear must be\ngreater than 0 or the result is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [MAKETIME] declaration=hour,minute,second category=Date and Time Functions -description=Returns a time value calculated from the hour, minute, and second arguments +description=Returns a time value calculated from the hour, minute, and second\narguments.\n\nThe second argument can have a fractional part.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [MAKE_SET] declaration=bits,str1,str2,... category=String Functions -description=Returns a set value (a string containing substrings separated by ,\ncharacters) consisting of the strings that have the corresponding bit in\nbits set +description=Returns a set value (a string containing substrings separated by ","\ncharacters) consisting of the strings that have the corresponding bit\nin bits set. str1 corresponds to bit 0, str2 to bit 1, and so on. NULL\nvalues in str1, str2, ... are not appended to the result.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [MASTER_POS_WAIT] -declaration=log_name,log_pos[,timeout][,channel] +declaration=log_name,log_pos[,timeout] category=Miscellaneous Functions -description=This function is useful for control of source/replica synchronization +description=This function is useful for control of master/slave synchronization. It\nblocks until the slave has read and applied all updates up to the\nspecified position in the master log. The return value is the number of\nlog events the slave had to wait for to advance to the specified\nposition. The function returns NULL if the slave SQL thread is not\nstarted, the slave's master information is not initialized, the\narguments are incorrect, or an error occurs. It returns -1 if the\ntimeout has been exceeded. If the slave SQL thread stops while\nMASTER_POS_WAIT() is waiting, the function returns NULL. If the slave\nis past the specified position, the function returns immediately.\n\nIf a timeout value is specified, MASTER_POS_WAIT() stops waiting when\ntimeout seconds have elapsed. timeout must be greater than 0; a zero or\nnegative timeout means no timeout.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [MAX] declaration=[DISTINCT] expr -category=Aggregate Functions and Modifiers -description=Returns the maximum value of expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the maximum value of expr. MAX() may take a string argument; in\nsuch cases, it returns the maximum string value. See\nhttp://dev.mysql.com/doc/refman/5.7/en/mysql-indexes.html. The DISTINCT\nkeyword can be used to find the maximum of the distinct values of expr,\nhowever, this produces the same result as omitting DISTINCT.\n\nMAX() returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [MBRCONTAINS] -declaration=g1, g2 -category=MBR Functions -description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1\ncontains the minimum bounding rectangle of g2 +declaration=g1,g2 +category=MBR +description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1\ncontains the minimum bounding rectangle of g2. This tests the opposite\nrelationship as MBRWithin().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mysql-specific.html [MBRCOVEREDBY] -declaration=g1, g2 -category=MBR Functions -description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1 is\ncovered by the minimum bounding rectangle of g2 +declaration=g1,g2 +category=MBR +description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1\nis covered by the minimum bounding rectangle of g2. This tests the\nopposite relationship as MBRCovers().\n\nMBRCoveredBy() and MBRCovers() handle their arguments and return a\nvalue as follows:\n\no Return NULL if either argument is NULL or an empty geometry\n\no Return ER_GIS_INVALID_DATA if either argument is not a valid geometry\n byte string (SRID plus WKB value)\n\no Otherwise, return non-NULL\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mysql-specific.html [MBRCOVERS] -declaration=g1, g2 -category=MBR Functions -description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1\ncovers the minimum bounding rectangle of g2 +declaration=g1,g2 +category=MBR +description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1\ncovers the minimum bounding rectangle of g2. This tests the opposite\nrelationship as MBRCoveredBy(). See the description of MBRCoveredBy()\nfor examples and information about argument handling.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mysql-specific.html [MBRDISJOINT] -declaration=g1, g2 -category=MBR Functions -description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of the\ntwo geometries g1 and g2 are disjoint (do not intersect) +declaration=g1,g2 +category=MBR +description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of\nthe two geometries g1 and g2 are disjoint (do not intersect).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mysql-specific.html +[MBREQUAL] +declaration=g1,g2 +category=MBR +description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of\nthe two geometries g1 and g2 are the same.\n\nThis function is deprecated as of MySQL 5.7.6 and will be removed in a\nfuture MySQL release. Use MBREquals() instead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mysql-specific.html [MBREQUALS] -declaration=g1, g2 -category=MBR Functions -description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of the\ntwo geometries g1 and g2 are the same +declaration=g1,g2 +category=MBR +description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of\nthe two geometries g1 and g2 are the same.\n\nThis function was added in MySQL 5.7.6. It should be used in preference\nto MBREqual().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mysql-specific.html [MBRINTERSECTS] -declaration=g1, g2 -category=MBR Functions -description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of the\ntwo geometries g1 and g2 intersect +declaration=g1,g2 +category=MBR +description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of\nthe two geometries g1 and g2 intersect.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mysql-specific.html [MBROVERLAPS] -declaration=g1, g2 -category=MBR Functions -description=Two geometries spatially overlap if they intersect and their intersection\nresults in a geometry of the same dimension but not equal to either of the\ngiven geometries +declaration=g1,g2 +category=MBR +description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of\nthe two geometries g1 and g2 overlap. The term spatially overlaps is\nused if two geometries intersect and their intersection results in a\ngeometry of the same dimension but not equal to either of the given\ngeometries.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mysql-specific.html [MBRTOUCHES] -declaration=g1, g2 -category=MBR Functions -description=Two geometries spatially touch if their interiors do not intersect, but the\nboundary of one of the geometries intersects either the boundary or the\ninterior of the other +declaration=g1,g2 +category=MBR +description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of\nthe two geometries g1 and g2 touch. Two geometries spatially touch if\nthe interiors of the geometries do not intersect, but the boundary of\none of the geometries intersects either the boundary or the interior of\nthe other.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mysql-specific.html [MBRWITHIN] -declaration=g1, g2 -category=MBR Functions -description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1 is\nwithin the minimum bounding rectangle of g2 +declaration=g1,g2 +category=MBR +description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1\nis within the minimum bounding rectangle of g2. This tests the opposite\nrelationship as MBRContains().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mysql-specific.html [MD5] declaration=str category=Encryption Functions -description=Calculates an MD5 128-bit checksum for the string +description=Calculates an MD5 128-bit checksum for the string. The value is\nreturned as a string of 32 hex digits, or NULL if the argument was\nNULL. The return value can, for example, be used as a hash key. See the\nnotes at the beginning of this section about storing hash values\nefficiently.\n\nThe return value is a nonbinary string in the connection character set.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html +[MEDIUMINT] +declaration=M +category=Data Types +description=A medium-sized integer. The signed range is -8388608 to 8388607. The\nunsigned range is 0 to 16777215.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html [MICROSECOND] declaration=expr category=Date and Time Functions -description=Returns the microseconds from the time or datetime expression expr as a\nnumber in the range from 0 to 999999 +description=Returns the microseconds from the time or datetime expression expr as a\nnumber in the range from 0 to 999999.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [MID] declaration=str,pos,len category=String Functions -description=MID(str,pos,len) is a synonym for SUBSTRING(str,pos,len) +description=MID(str,pos,len) is a synonym for SUBSTRING(str,pos,len).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [MIN] declaration=[DISTINCT] expr -category=Aggregate Functions and Modifiers -description=Returns the minimum value of expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the minimum value of expr. MIN() may take a string argument; in\nsuch cases, it returns the minimum string value. See\nhttp://dev.mysql.com/doc/refman/5.7/en/mysql-indexes.html. The DISTINCT\nkeyword can be used to find the minimum of the distinct values of expr,\nhowever, this produces the same result as omitting DISTINCT.\n\nMIN() returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [MINUTE] declaration=time category=Date and Time Functions -description=Returns the minute for time, in the range 0 to 59 +description=Returns the minute for time, in the range 0 to 59.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[MLINEFROMTEXT] +declaration=wkt[,srid] +category=WKT +description=Constructs a MultiLineString value using its WKT representation and\nSRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkt-functions.html +[MLINEFROMWKB] +declaration=wkb[,srid] +category=WKB +description=Constructs a MultiLineString value using its WKB representation and\nSRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkb-functions.html [MOD] declaration=N,M category=Numeric Functions -description=Modulo operation +description=Modulo operation. Returns the remainder of N divided by M.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [MONTH] declaration=date category=Date and Time Functions -description=Returns the month for date, in the range 1 to 12 for January to December,\nor 0 for dates such as '0000-00-00' or '2008-00-00' that have a zero month\npart +description=Returns the month for date, in the range 1 to 12 for January to\nDecember, or 0 for dates such as '0000-00-00' or '2008-00-00' that have\na zero month part.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [MONTHNAME] declaration=date category=Date and Time Functions -description=Returns the full name of the month for date +description=Returns the full name of the month for date. The language used for the\nname is controlled by the value of the lc_time_names system variable\n(http://dev.mysql.com/doc/refman/5.7/en/locale-support.html).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[MPOINTFROMTEXT] +declaration=wkt[,srid] +category=WKT +description=Constructs a MultiPoint value using its WKT representation and SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkt-functions.html +[MPOINTFROMWKB] +declaration=wkb[,srid] +category=WKB +description=Constructs a MultiPoint value using its WKB representation and SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkb-functions.html +[MPOLYFROMTEXT] +declaration=wkt[,srid] +category=WKT +description=Constructs a MultiPolygon value using its WKT representation and SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkt-functions.html +[MPOLYFROMWKB] +declaration=wkb[,srid] +category=WKB +description=Constructs a MultiPolygon value using its WKB representation and SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkb-functions.html [MULTILINESTRING] -declaration=ls [, ls] ... -category=Geometry Constructors -description=Constructs a MultiLineString value using LineString or WKB LineString\narguments +declaration=ls1,ls2,... +category=Geometry constructors +description=Constructs a MultiLineString value using LineString or WKB LineString\narguments.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-mysql-specific-functions.html [MULTIPOINT] -declaration=pt [, pt2] ... -category=Geometry Constructors -description=Constructs a MultiPoint value using Point or WKB Point arguments +declaration=pt1,pt2,... +category=Geometry constructors +description=Constructs a MultiPoint value using Point or WKB Point arguments.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-mysql-specific-functions.html [MULTIPOLYGON] -declaration=poly [, poly] ... -category=Geometry Constructors -description=Constructs a MultiPolygon value from a set of Polygon or WKB Polygon\narguments +declaration=poly1,poly2,... +category=Geometry constructors +description=Constructs a MultiPolygon value from a set of Polygon or WKB Polygon\narguments.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-mysql-specific-functions.html [NAME_CONST] declaration=name,value category=Miscellaneous Functions -description=Returns the given value +description=Returns the given value. When used to produce a result set column,\nNAME_CONST() causes the column to have the given name. The arguments\nshould be constants.\n\nmysql> SELECT NAME_CONST('myname', 14);\n+--------+\n| myname |\n+--------+\n| 14 |\n+--------+\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [NOW] declaration=[fsp] category=Date and Time Functions -description=Returns the current date and time as a value in 'YYYY-MM-DD hh:mm:ss' or\nYYYYMMDDhhmmss format, depending on whether the function is used in string\nor numeric context -[NTH_VALUE] -declaration=expr, N -category=Window Functions -description=Returns the value of expr from the N-th row of the window frame -[NTILE] -declaration=N -category=Window Functions -description=Divides a partition into N groups (buckets), assigns each row in the\npartition its bucket number, and returns the bucket number of the current\nrow within its partition +description=Returns the current date and time as a value in 'YYYY-MM-DD HH:MM:SS'\nor YYYYMMDDHHMMSS format, depending on whether the function is used in\na string or numeric context. The value is expressed in the current time\nzone.\n\nIf the fsp argument is given to specify a fractional seconds precision\nfrom 0 to 6, the return value includes a fractional seconds part of\nthat many digits.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [NULLIF] declaration=expr1,expr2 -category=Flow Control Functions -description=Returns NULL if expr1 = expr2 is true, otherwise returns expr1 +category=Control flow functions +description=Returns NULL if expr1 = expr2 is true, otherwise returns expr1. This is\nthe same as CASE WHEN expr1 = expr2 THEN NULL ELSE expr1 END.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/control-flow-functions.html +[NUMGEOMETRIES] +declaration=gc +category=GeometryCollection properties +description=Returns the number of geometries in the GeometryCollection value gc.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-geometrycollection-property-functions.html +[NUMINTERIORRINGS] +declaration=poly +category=Polygon properties +description=Returns the number of interior rings in the Polygon value poly.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-polygon-property-functions.html +[NUMPOINTS] +declaration=ls +category=LineString properties +description=Returns the number of Point objects in the LineString value ls.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-linestring-property-functions.html [OCT] declaration=N category=String Functions -description=Returns a string representation of the octal value of N, where N is a\nlonglong (BIGINT) number +description=Returns a string representation of the octal value of N, where N is a\nlonglong (BIGINT) number. This is equivalent to CONV(N,10,8). Returns\nNULL if N is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [OCTET_LENGTH] declaration=str category=String Functions -description=OCTET_LENGTH() is a synonym for LENGTH() +description=OCTET_LENGTH() is a synonym for LENGTH().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[OLD_PASSWORD] +declaration=str +category=Encryption Functions +description=OLD_PASSWORD() was added when the implementation of PASSWORD() was\nchanged in MySQL 4.1 to improve security. OLD_PASSWORD() returns the\nvalue of the pre-4.1 implementation of PASSWORD() as a string, and is\nintended to permit you to reset passwords for any pre-4.1 clients that\nneed to connect to your version 5.7 MySQL server without locking them\nout. See http://dev.mysql.com/doc/refman/5.7/en/password-hashing.html.\n\nThe return value is a nonbinary string in the connection character set.\n\n*Note*: Passwords that use the pre-4.1 hashing method are less secure\nthan passwords that use the native password hashing method and should\nbe avoided. Pre-4.1 passwords are deprecated and support for them is\nremoved in MySQL 5.7.5. Consequently, OLD_PASSWORD() is deprecated and\nis removed in MySQL 5.7.5.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html [ORD] declaration=str category=String Functions -description=If the leftmost character of the string str is a multibyte character,\nreturns the code for that character, calculated from the numeric values of\nits constituent bytes using this formula: (1st byte code) + (2nd byte\ncode * 256) + (3rd byte code * 256^2) +description=If the leftmost character of the string str is a multibyte character,\nreturns the code for that character, calculated from the numeric values\nof its constituent bytes using this formula:\n\n (1st byte code)\n+ (2nd byte code * 256)\n+ (3rd byte code * 2562) ...\n\nIf the leftmost character is not a multibyte character, ORD() returns\nthe same value as the ASCII() function.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[OVERLAPS] +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 spatially overlaps g2. The term\nspatially overlaps is used if two geometries intersect and their\nintersection results in a geometry of the same dimension but not equal\nto either of the given geometries.\n\nThis function is deprecated as of MySQL 5.7.6 and will be removed in a\nfuture MySQL release. Use MBROverlaps() instead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html +[PASSWORD] +declaration=str +category=Encryption Functions +description=*Note*: This function is deprecated as of MySQL 5.7.6 and will be\nremoved in a future MySQL release.\n\nReturns a hashed password string calculated from the cleartext password\nstr. The return value is a nonbinary string in the connection character\nset, or NULL if the argument is NULL. This function is the SQL\ninterface to the algorithm used by the server to encrypt MySQL\npasswords for storage in the mysql.user grant table.\n\nThe old_passwords system variable controls the password hashing method\nused by the PASSWORD() function. It also influences password hashing\nperformed by CREATE USER and GRANT statements that specify a password\nusing an IDENTIFIED BY clause.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html [PERIOD_ADD] declaration=P,N category=Date and Time Functions -description=Adds N months to period P (in the format YYMM or YYYYMM) +description=Adds N months to period P (in the format YYMM or YYYYMM). Returns a\nvalue in the format YYYYMM. Note that the period argument P is not a\ndate value.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [PERIOD_DIFF] declaration=P1,P2 category=Date and Time Functions -description=Returns the number of months between periods P1 and P2 +description=Returns the number of months between periods P1 and P2. P1 and P2\nshould be in the format YYMM or YYYYMM. Note that the period arguments\nP1 and P2 are not date values.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[PI] +declaration= +category=Numeric Functions +description=Returns the value of ? (pi). The default number of decimal places\ndisplayed is seven, but MySQL uses the full double-precision value\ninternally.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [POINT] -declaration=x, y -category=Geometry Constructors -description=Constructs a Point using its coordinates +declaration=x,y +category=Geometry constructors +description=Constructs a Point using its coordinates.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-mysql-specific-functions.html +[POINTFROMTEXT] +declaration=wkt[,srid] +category=WKT +description=Constructs a Point value using its WKT representation and SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkt-functions.html +[POINTFROMWKB] +declaration=wkb[,srid] +category=WKB +description=Constructs a Point value using its WKB representation and SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkb-functions.html +[POINTN] +declaration=ls,N +category=LineString properties +description=Returns the N-th Point in the Linestring value ls. Points are numbered\nbeginning with 1.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-linestring-property-functions.html +[POLYFROMTEXT] +declaration=wkt[,srid] +category=WKT +description=Constructs a Polygon value using its WKT representation and SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkt-functions.html +[POLYFROMWKB] +declaration=wkb[,srid] +category=WKB +description=Constructs a Polygon value using its WKB representation and SRID.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-wkb-functions.html [POLYGON] -declaration=ls [, ls] ... -category=Geometry Constructors -description=Constructs a Polygon value from a number of LineString or WKB LineString\narguments +declaration=ls1,ls2,... +category=Geometry constructors +description=Constructs a Polygon value from a number of LineString or WKB\nLineString arguments. If any argument does not represent a LinearRing\n(that is, not a closed and simple LineString), the return value is\nNULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-mysql-specific-functions.html [POSITION] declaration=substr IN str category=String Functions -description=POSITION(substr IN str) is a synonym for LOCATE(substr,str) +description=POSITION(substr IN str) is a synonym for LOCATE(substr,str).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [POW] declaration=X,Y category=Numeric Functions -description=Returns the value of X raised to the power of Y +description=Returns the value of X raised to the power of Y.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [POWER] declaration=X,Y category=Numeric Functions -description=This is a synonym for POW() -[PS_THREAD_ID] -declaration=connection_id -category=Performance Schema Functions -description=Given a connection ID, returns a BIGINT UNSIGNED value representing the\nPerformance Schema thread ID assigned to the connection ID, or NULL if no\nthread ID exists for the connection ID +description=This is a synonym for POW().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [QUARTER] declaration=date category=Date and Time Functions -description=Returns the quarter of the year for date, in the range 1 to 4 +description=Returns the quarter of the year for date, in the range 1 to 4.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [QUOTE] declaration=str category=String Functions -description=Quotes a string to produce a result that can be used as a properly escaped\ndata value in an SQL statement +description=Quotes a string to produce a result that can be used as a properly\nescaped data value in an SQL statement. The string is returned enclosed\nby single quotation marks and with each instance of backslash ("\"),\nsingle quote ("'"), ASCII NUL, and Control+Z preceded by a backslash.\nIf the argument is NULL, the return value is the word "NULL" without\nenclosing single quotation marks.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [RADIANS] declaration=X category=Numeric Functions -description=Returns the argument X, converted from degrees to radians +description=Returns the argument X, converted from degrees to radians. (Note that\n? radians equals 180 degrees.)\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [RAND] -declaration=[N] +declaration= category=Numeric Functions -description=Returns a random floating-point value v in the range 0 <= v < 1 +description=Returns a random floating-point value v in the range 0 <= v < 1.0. If a\nconstant integer argument N is specified, it is used as the seed value,\nwhich produces a repeatable sequence of column values. In the following\nexample, note that the sequences of values produced by RAND(3) is the\nsame both places where it occurs.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [RANDOM_BYTES] declaration=len category=Encryption Functions -description=This function returns a binary string of len random bytes generated using\nthe random number generator of the SSL library -[REGEXP_INSTR] -declaration=expr, pat[, pos[, occurrence[, return_option[, -match_type]]]] -category=String Functions -description=Returns the starting index of the substring of the string expr that matches\nthe regular expression specified by the pattern pat, 0 if there is no match -[REGEXP_LIKE] -declaration=expr, pat[, match_type] -category=String Functions -description=Returns 1 if the string expr matches the regular expression specified by\nthe pattern pat, 0 otherwise -[REGEXP_REPLACE] -declaration=expr, pat, repl[, pos[, occurrence[, match_type]]] -category=String Functions -description=Replaces occurrences in the string expr that match the regular expression\nspecified by the pattern pat with the replacement string repl, and returns\nthe resulting string -[REGEXP_SUBSTR] -declaration=expr, pat[, pos[, occurrence[, match_type]]] -category=String Functions -description=Returns the substring of the string expr that matches the regular\nexpression specified by the pattern pat, NULL if there is no match +description=This function returns a binary string of len random bytes generated\nusing the random number generator of the SSL library (OpenSSL or\nyaSSL). Permitted values of len range from 1 to 1024. For values\noutside that range, RANDOM_BYTES() generates a warning and returns\nNULL.\n\nRANDOM_BYTES() can be used to provide the initialization vector for the\nAES_DECRYPT() and AES_ENCRYPT() functions. For use in that context, len\nmust be at least 16. Larger values are permitted, but bytes in excess\nof 16 are ignored.\n\nRANDOM_BYTES() generates a random value, which makes its result\nnondeterministic. Consequently, statements that use this function are\nunsafe for statement-based replication and cannot be stored in the\nquery cache.\n\nThis function is available as of MySQL 5.7.4.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html +[RELEASE_ALL_LOCKS] +declaration= +category=Miscellaneous Functions +description=Releases all named locks held by the current session and returns the\nnumber of locks released (0 if there were none)\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [RELEASE_LOCK] declaration=str -category=Locking Functions -description=Releases the lock named by the string str that was obtained with GET_LOCK() +category=Miscellaneous Functions +description=Releases the lock named by the string str that was obtained with\nGET_LOCK(). Returns 1 if the lock was released, 0 if the lock was not\nestablished by this thread (in which case the lock is not released),\nand NULL if the named lock did not exist. The lock does not exist if it\nwas never obtained by a call to GET_LOCK() or if it has previously been\nreleased.\n\nThe DO statement is convenient to use with RELEASE_LOCK(). See [HELP\nDO].\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [REVERSE] declaration=str category=String Functions -description=Returns the string str with the order of the characters reversed +description=Returns the string str with the order of the characters reversed.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [RIGHT] declaration=str,len category=String Functions -description=Returns the rightmost len characters from the string str, or NULL if any\nargument is NULL +description=Returns the rightmost len characters from the string str, or NULL if\nany argument is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [ROUND] declaration=X category=Numeric Functions -description=Rounds the argument X to D decimal places +description=Rounds the argument X to D decimal places. The rounding algorithm\ndepends on the data type of X. D defaults to 0 if not specified. D can\nbe negative to cause D digits left of the decimal point of the value X\nto become zero.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html +[ROW_COUNT] +declaration= +category=Information Functions +description=In MySQL 5.7, ROW_COUNT() returns a value as follows:\n\no DDL statements: 0. This applies to statements such as CREATE TABLE or\n DROP TABLE.\n\no DML statements other than SELECT: The number of affected rows. This\n applies to statements such as UPDATE, INSERT, or DELETE (as before),\n but now also to statements such as ALTER TABLE and LOAD DATA INFILE.\n\no SELECT: -1 if the statement returns a result set, or the number of\n rows "affected" if it does not. For example, for SELECT * FROM t1,\n ROW_COUNT() returns -1. For SELECT * FROM t1 INTO OUTFILE\n 'file_name', ROW_COUNT() returns the number of rows written to the\n file.\n\no SIGNAL statements: 0.\n\nFor UPDATE statements, the affected-rows value by default is the number\nof rows actually changed. If you specify the CLIENT_FOUND_ROWS flag to\nmysql_real_connect() when connecting to mysqld, the affected-rows value\nis the number of rows "found"; that is, matched by the WHERE clause.\n\nFor REPLACE statements, the affected-rows value is 2 if the new row\nreplaced an old row, because in this case, one row was inserted after\nthe duplicate was deleted.\n\nFor INSERT ... ON DUPLICATE KEY UPDATE statements, the affected-rows\nvalue per row is 1 if the row is inserted as a new row, 2 if an\nexisting row is updated, and 0 if an existing row is set to its current\nvalues. If you specify the CLIENT_FOUND_ROWS flag, the affected-rows\nvalue is 1 (not 0) if an existing row is set to its current values.\n\nThe ROW_COUNT() value is similar to the value from the\nmysql_affected_rows() C API function and the row count that the mysql\nclient displays following statement execution.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [RPAD] declaration=str,len,padstr category=String Functions -description=Returns the string str, right-padded with the string padstr to a length of\nlen characters +description=Returns the string str, right-padded with the string padstr to a length\nof len characters. If str is longer than len, the return value is\nshortened to len characters.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [RTRIM] declaration=str category=String Functions -description=Returns the string str with trailing space characters removed +description=Returns the string str with trailing space characters removed.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[SCHEMA] +declaration= +category=Information Functions +description=This function is a synonym for DATABASE().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [SECOND] declaration=time category=Date and Time Functions -description=Returns the second for time, in the range 0 to 59 +description=Returns the second for time, in the range 0 to 59.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [SEC_TO_TIME] declaration=seconds category=Date and Time Functions -description=Returns the seconds argument, converted to hours, minutes, and seconds, as\na TIME value +description=Returns the seconds argument, converted to hours, minutes, and seconds,\nas a TIME value. The range of the result is constrained to that of the\nTIME data type. A warning occurs if the argument corresponds to a value\noutside that range.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[SESSION_USER] +declaration= +category=Information Functions +description=SESSION_USER() is a synonym for USER().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [SHA1] declaration=str category=Encryption Functions -description=Calculates an SHA-1 160-bit checksum for the string, as described in RFC\n3174 (Secure Hash Algorithm) +description=Calculates an SHA-1 160-bit checksum for the string, as described in\nRFC 3174 (Secure Hash Algorithm). The value is returned as a string of\n40 hex digits, or NULL if the argument was NULL. One of the possible\nuses for this function is as a hash key. See the notes at the beginning\nof this section about storing hash values efficiently. You can also use\nSHA1() as a cryptographic function for storing passwords. SHA() is\nsynonymous with SHA1().\n\nThe return value is a nonbinary string in the connection character set.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html [SHA2] declaration=str, hash_length category=Encryption Functions -description=Calculates the SHA-2 family of hash functions (SHA-224, SHA-256, SHA-384,\nand SHA-512) +description=Calculates the SHA-2 family of hash functions (SHA-224, SHA-256,\nSHA-384, and SHA-512). The first argument is the cleartext string to be\nhashed. The second argument indicates the desired bit length of the\nresult, which must have a value of 224, 256, 384, 512, or 0 (which is\nequivalent to 256). If either argument is NULL or the hash length is\nnot one of the permitted values, the return value is NULL. Otherwise,\nthe function result is a hash value containing the desired number of\nbits. See the notes at the beginning of this section about storing hash\nvalues efficiently.\n\nThe return value is a nonbinary string in the connection character set.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html [SIGN] declaration=X category=Numeric Functions -description=Returns the sign of the argument as -1, 0, or 1, depending on whether X is\nnegative, zero, or positive +description=Returns the sign of the argument as -1, 0, or 1, depending on whether X\nis negative, zero, or positive.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [SIN] declaration=X category=Numeric Functions -description=Returns the sine of X, where X is given in radians +description=Returns the sine of X, where X is given in radians.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [SLEEP] declaration=duration category=Miscellaneous Functions -description=Sleeps (pauses) for the number of seconds given by the duration argument,\nthen returns 0 +description=Sleeps (pauses) for the number of seconds given by the duration\nargument, then returns 0. If SLEEP() is interrupted, it returns 1. The\nduration may have a fractional part. If the argument is NULL or\nnegative, SLEEP() produces a warning, or an error in strict SQL mode.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html +[SMALLINT] +declaration=M +category=Data Types +description=A small integer. The signed range is -32768 to 32767. The unsigned\nrange is 0 to 65535.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html [SOUNDEX] declaration=str category=String Functions -description=Returns a soundex string from str +description=Returns a soundex string from str. Two strings that sound almost the\nsame should have identical soundex strings. A standard soundex string\nis four characters long, but the SOUNDEX() function returns an\narbitrarily long string. You can use SUBSTRING() on the result to get a\nstandard soundex string. All nonalphabetic characters in str are\nignored. All international alphabetic characters outside the A-Z range\nare treated as vowels.\n\n*Important*: When using SOUNDEX(), you should be aware of the following\nlimitations:\n\no This function, as currently implemented, is intended to work well\n with strings that are in the English language only. Strings in other\n languages may not produce reliable results.\n\no This function is not guaranteed to provide consistent results with\n strings that use multibyte character sets, including utf-8.\n\n We hope to remove these limitations in a future release. See Bug\n #22638 for more information.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [SPACE] declaration=N category=String Functions -description=Returns a string consisting of N space characters +description=Returns a string consisting of N space characters.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [SQRT] declaration=X category=Numeric Functions -description=Returns the square root of a nonnegative number X -[STATEMENT_DIGEST] -declaration=statement -category=Encryption Functions -description=Given an SQL statement as a string, returns the statement digest hash value\nas a string in the connection character set, or NULL if the argument is\nNULL -[STATEMENT_DIGEST_TEXT] -declaration=statement -category=Encryption Functions -description=Given an SQL statement as a string, returns the normalized statement digest\nas a string in the connection character set, or NULL if the argument is\nNULL +description=Returns the square root of a nonnegative number X.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html +[SRID] +declaration=g +category=Geometry properties +description=Returns an integer indicating the Spatial Reference System ID for the\ngeometry value g.\n\nIn MySQL, the SRID value is just an integer associated with the\ngeometry value. All calculations are done assuming Euclidean (planar)\ngeometry.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-general-property-functions.html +[STARTPOINT] +declaration=ls +category=LineString properties +description=Returns the Point that is the start point of the LineString value ls.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-linestring-property-functions.html [STD] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns the population standard deviation of expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the population standard deviation of expr. This is an extension\nto standard SQL. The standard SQL function STDDEV_POP() can be used\ninstead.\n\nThis function returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [STDDEV] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns the population standard deviation of expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the population standard deviation of expr. This function is\nprovided for compatibility with Oracle. The standard SQL function\nSTDDEV_POP() can be used instead.\n\nThis function returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [STDDEV_POP] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns the population standard deviation of expr (the square root of\nVAR_POP()) +category=Functions and Modifiers for Use with GROUP BY +description=Returns the population standard deviation of expr (the square root of\nVAR_POP()). You can also use STD() or STDDEV(), which are equivalent\nbut not standard SQL.\n\nSTDDEV_POP() returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [STDDEV_SAMP] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns the sample standard deviation of expr (the square root of\nVAR_SAMP() +category=Functions and Modifiers for Use with GROUP BY +description=Returns the sample standard deviation of expr (the square root of\nVAR_SAMP().\n\nSTDDEV_SAMP() returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [STRCMP] declaration=expr1,expr2 category=String Functions -description=STRCMP() returns 0 if the strings are the same, -1 if the first argument is\nsmaller than the second according to the current sort order, and 1\notherwise +description=STRCMP() returns 0 if the strings are the same, -1 if the first\nargument is smaller than the second according to the current sort\norder, and 1 otherwise.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-comparison-functions.html [STR_TO_DATE] declaration=str,format category=Date and Time Functions -description=This is the inverse of the DATE_FORMAT() function +description=This is the inverse of the DATE_FORMAT() function. It takes a string\nstr and a format string format. STR_TO_DATE() returns a DATETIME value\nif the format string contains both date and time parts, or a DATE or\nTIME value if the string contains only date or time parts. If the date,\ntime, or datetime value extracted from str is illegal, STR_TO_DATE()\nreturns NULL and produces a warning.\n\nThe server scans str attempting to match format to it. The format\nstring can contain literal characters and format specifiers beginning\nwith %. Literal characters in format must match literally in str.\nFormat specifiers in format must match a date or time part in str. For\nthe specifiers that can be used in format, see the DATE_FORMAT()\nfunction description.\n\nmysql> SELECT STR_TO_DATE('01,5,2013','%d,%m,%Y');\n -> '2013-05-01'\nmysql> SELECT STR_TO_DATE('May 1, 2013','%M %d,%Y');\n -> '2013-05-01'\n\nScanning starts at the beginning of str and fails if format is found\nnot to match. Extra characters at the end of str are ignored.\n\nmysql> SELECT STR_TO_DATE('a09:30:17','a%h:%i:%s');\n -> '09:30:17'\nmysql> SELECT STR_TO_DATE('a09:30:17','%h:%i:%s');\n -> NULL\nmysql> SELECT STR_TO_DATE('09:30:17a','%h:%i:%s');\n -> '09:30:17'\n\nUnspecified date or time parts have a value of 0, so incompletely\nspecified values in str produce a result with some or all parts set to\n0:\n\nmysql> SELECT STR_TO_DATE('abc','abc');\n -> '0000-00-00'\nmysql> SELECT STR_TO_DATE('9','%m');\n -> '0000-09-00'\nmysql> SELECT STR_TO_DATE('9','%s');\n -> '00:00:09'\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [ST_AREA] -declaration={poly|mpoly} -category=Polygon Property Functions -description=Returns a double-precision number indicating the area of the Polygon or\nMultiPolygon argument, as measured in its spatial reference system -[ST_ASBINARY] -declaration=g [, options] -category=WKB Functions -description=Converts a value in internal geometry format to its WKB representation and\nreturns the binary result +declaration=poly +category=Polygon properties +description=Returns a double-precision number indicating the area of the argument,\nas measured in its spatial reference system. For arguments of dimension\n0 or 1, the result is 0.\n\nAdditionally, as of MySQL 5.7.5: The result is the sum of the area\nvalues of all components for a geometry collection. If a geometry\ncollection is empty, its area is returned as 0.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-polygon-property-functions.html [ST_ASGEOJSON] declaration=g [, max_dec_digits [, options]] -category=MBR Functions -description=Generates a GeoJSON object from the geometry g -[ST_ASTEXT] -declaration=g [, options] -category=WKB Functions -description=Converts a value in internal geometry format to its WKT representation and\nreturns the string result -[ST_BUFFER] -declaration=g, d [, strategy1 [, strategy2 [, strategy3]]] -category=GeometryCollection Property Functions -description=Returns a geometry that represents all points whose distance from the\ngeometry value g is less than or equal to a distance of d -[ST_BUFFER_STRATEGY] -declaration=strategy [, points_per_circle] -category=GeometryCollection Property Functions -description=This function returns a strategy byte string for use with ST_Buffer() to\ninfluence buffer computation +category=MBR +description=Generates a GeoJSON object from the geometry g. The object string has\nthe connection character set and collation.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-geojson-functions.html [ST_CENTROID] -declaration={poly|mpoly} -category=Polygon Property Functions -description=Returns the mathematical centroid for the Polygon or MultiPolygon argument\nas a Point -[ST_COLLECT] -declaration=[DISTINCT] g -category=MBR Functions -description=Aggregates geometry values and returns a single geometry collection value +declaration=mpoly +category=Polygon properties +description=Returns the mathematical centroid for the MultiPolygon value mpoly as a\nPoint. The result is not guaranteed to be on the MultiPolygon.\n\nAs of MySQL 5.7.5, this function processes geometry collections by\ncomputing the centroid point for components of highest dimension in the\ncollection. Such components are extracted and made into a single\nMultiPolygon, MultiLineString, or MultiPoint for centroid computation.\nIf the argument is an empty geometry collection, the return value is\nNULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-multipolygon-property-functions.html [ST_CONTAINS] -declaration=g1, g2 -category=Geometry Relation Functions -description=Returns 1 or 0 to indicate whether g1 completely contains g2 +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 completely contains g2. This\ntests the opposite relationship as ST_Within().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-object-shapes.html [ST_CONVEXHULL] declaration=g -category=GeometryCollection Property Functions -description=Returns a geometry that represents the convex hull of the geometry value g +category=GeometryCollection properties +description=Returns a geometry that represents the convex hull of the geometry\nvalue g.\n\nThis function computes a geometry's convex hull by first checking\nwhether its vertex points are colinear. The function returns a linear\nhull if so, a polygon hull otherwise. This function processes geometry\ncollections by extracting all vertex points of all components of the\ncollection, creating a MultiPoint value from them, and computing its\nconvex hull. If the argument is an empty geometry collection, the\nreturn value is NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-operator-functions.html [ST_CROSSES] -declaration=g1, g2 -category=Geometry Relation Functions -description=Two geometries spatially cross if their spatial relation has the following\nproperties: o Unless g1 and g2 are both of dimension 1: g1 crosses g2 if\nthe interior of g2 has points in common with the interior of g1, but g2 \ndoes not cover the entire interior of g1 +declaration=g1,g2 +category=Geometry relations +description=Returns 1 if g1 spatially crosses g2. Returns NULL if g1 is a Polygon\nor a MultiPolygon, or if g2 is a Point or a MultiPoint. Otherwise,\nreturns 0.\n\nAs of MySQL 5.7.5, this function returns 0 if called with an\ninapplicable geometry argument type combination. For example, it\nreturns 0 if the first argument is a Polygon or MultiPolygon and/or the\nsecond argument is a Point or MultiPoint.\n\nThe term spatially crosses denotes a spatial relation between two given\ngeometries that has the following properties:\n\no The two geometries intersect\n\no Their intersection results in a geometry that has a dimension that is\n one less than the maximum dimension of the two given geometries\n\no Their intersection is not equal to either of the two given geometries\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-object-shapes.html [ST_DIFFERENCE] declaration=g1, g2 -category=GeometryCollection Property Functions -description=Returns a geometry that represents the point set difference of the geometry\nvalues g1 and g2 -[ST_DIMENSION] -declaration=g -category=Geometry Property Functions -description=Returns the inherent dimension of the geometry value g +category=GeometryCollection properties +description=Returns a geometry that represents the point set difference of the\ngeometry values g1 and g2.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-operator-functions.html [ST_DISJOINT] -declaration=g1, g2 -category=Geometry Relation Functions -description=Returns 1 or 0 to indicate whether g1 is spatially disjoint from (does not\nintersect) g2 +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 is spatially disjoint from (does\nnot intersect) g2.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-object-shapes.html [ST_DISTANCE] -declaration=g1, g2 [, unit] -category=Geometry Relation Functions -description=Returns the distance between g1 and g2, measured in the length unit of the\nspatial reference system (SRS) of the geometry arguments, or in the unit of\nthe optional unit argument if that is specified +declaration=g1,g2 +category=Geometry relations +description=Returns the distance between g1 and g2.\n\nAs of MySQL 5.7.5, this function processes geometry collections by\nreturning the shortest distance among all combinations of the\ncomponents of the two geometry arguments. If either argument is an\nempty geometry collection, the return value is NULL.\n\nAs of MySQL 5.7.6, if an intermediate or final result produces NaN or a\nnegative number, this function produces a ER_GIS_INVALID_DATA error.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-object-shapes.html [ST_DISTANCE_SPHERE] declaration=g1, g2 [, radius] -category=MBR Functions -description=Returns the mimimum spherical distance between Point or MultiPoint\narguments on a sphere, in meters -[ST_ENDPOINT] -declaration=ls -category=LineString Property Functions -description=Returns the Point that is the endpoint of the LineString value ls +category=MBR +description=Returns the mimimum spherical distance between two points and/or\nmultipoints on a sphere, in meters, or NULL if any geometry argument is\nNULL or empty.\n\nCalculations use a spherical earth and a configurable radius. The\noptional radius argument should be given in meters. If omitted, the\ndefault radius is 6,370,986 meters. An ER_WRONG_ARGUMENTS error occurs\nif the radius argument is present but not positive.\n\nThe geometry arguments should consist of points that specify\n(longitude, latitude) coordinate values:\n\no Longitude and latitude are the first and second coordinates of the\n point, respectively.\n\no Both coordinates are in degrees.\n\no Longitude values must be in the range (-180, 180]. Positive values\n are east of the prime meridian.\n\no Latitude values must be in the range [-90, 90]. Positive values are\n north of the equator.\n\nSupported argument combinations are (Point, Point), (Point,\nMultiPoint), and (MultiPoint, Point). An ER_GIS_UNSUPPORTED_ARGUMENT\nerror occurs for other combinations.\n\nAn ER_GIS_INVALID_DATA error occurs if any geometry argument is not a\nvalid geometry byte string.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-convenience-functions.html [ST_ENVELOPE] declaration=g -category=Geometry Property Functions -description=Returns the minimum bounding rectangle (MBR) for the geometry value g +category=Geometry properties +description=Returns the minimum bounding rectangle (MBR) for the geometry value g.\nThe result is returned as a Polygon value that is defined by the corner\npoints of the bounding box:\n\nPOLYGON((MINX MINY, MAXX MINY, MAXX MAXY, MINX MAXY, MINX MINY))\n\nAs of MySQL 5.7.6, if the argument is a point or a vertical or\nhorizontal line segment, ST_Envelope() returns the point or the line\nsegment as its MBR rather than returning an invalid polygon.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-general-property-functions.html [ST_EQUALS] -declaration=g1, g2 -category=Geometry Relation Functions -description=Returns 1 or 0 to indicate whether g1 is spatially equal to g2 -[ST_EXTERIORRING] -declaration=poly -category=Polygon Property Functions -description=Returns the exterior ring of the Polygon value poly as a LineString -[ST_FRECHETDISTANCE] -declaration=g1, g2 [, unit] -category=Geometry Relation Functions -description=Returns the discrete Fr├®chet distance between two geometries, reflecting\nhow similar the geometries are +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 is spatially equal to g2.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-object-shapes.html [ST_GEOHASH] declaration=longitude, latitude, max_length -category=MBR Functions -description=max_length) Returns a geohash string in the connection character set and\ncollation -[ST_GEOMCOLLFROMTEXT] -declaration=wkt [, srid [, options]] -category=WKT Functions -description=ST_GeometryCollectionFromText(wkt [, srid [, options]]),\nST_GeomCollFromTxt(wkt [, srid [, options]]) Constructs a\nGeometryCollection value using its WKT representation and SRID -[ST_GEOMCOLLFROMWKB] -declaration=wkb [, srid [, options]] -category=WKB Functions -description=ST_GeometryCollectionFromWKB(wkb [, srid [, options]]) Constructs a\nGeometryCollection value using its WKB representation and SRID -[ST_GEOMETRYN] -declaration=gc, N -category=GeometryCollection Property Functions -description=Returns the N-th geometry in the GeometryCollection value gc -[ST_GEOMETRYTYPE] -declaration=g -category=Geometry Property Functions -description=Returns a binary string indicating the name of the geometry type of which\nthe geometry instance g is a member +category=MBR +description=max_length)\n\nReturns a geohash string in the connection character set and collation.\nThe result is NULL if any argument is NULL. An error occurs if any\nargument is invalid.\n\nFor the first syntax, the longitude must be a number in the range\n[-180, 180], and the latitude must be a number in the range [-90, 90].\nFor the second syntax, a POINT value is required, where the X and Y\ncoordinates are in the valid ranges for longitude and latitude,\nrespectively.\n\nThe resulting string is no longer than max_length characters, which has\nan upper limit of 100. The string might be shorter than max_length\ncharacters because the algorithm that creates the geohash value\ncontinues until it has created a string that is either an exact\nrepresentation of the location or max_length characters, whichever\ncomes first.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-geohash-functions.html [ST_GEOMFROMGEOJSON] declaration=str [, options [, srid]] -category=MBR Functions -description=Parses a string str representing a GeoJSON object and returns a geometry -[ST_GEOMFROMTEXT] -declaration=wkt [, srid [, options]] -category=WKT Functions -description=srid [, options]]) Constructs a geometry value of any type using its WKT\nrepresentation and SRID -[ST_GEOMFROMWKB] -declaration=wkb [, srid [, options]] -category=WKB Functions -description=srid [, options]]) Constructs a geometry value of any type using its WKB\nrepresentation and SRID -[ST_HAUSDORFFDISTANCE] -declaration=g1, g2 [, unit] -category=Geometry Relation Functions -description=Returns the discrete Hausdorff distance between two geometries, reflecting\nhow similar the geometries are -[ST_INTERIORRINGN] -declaration=poly, N -category=Polygon Property Functions -description=Returns the N-th interior ring for the Polygon value poly as a LineString +category=MBR +description=Parses a string str representing a GeoJSON object and returns a\ngeometry.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-geojson-functions.html [ST_INTERSECTION] declaration=g1, g2 -category=GeometryCollection Property Functions -description=Returns a geometry that represents the point set intersection of the\ngeometry values g1 and g2 +category=GeometryCollection properties +description=Returns a geometry that represents the point set intersection of the\ngeometry values g1 and g2.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-operator-functions.html [ST_INTERSECTS] -declaration=g1, g2 -category=Geometry Relation Functions -description=Returns 1 or 0 to indicate whether g1 spatially intersects g2 -[ST_ISCLOSED] -declaration=ls -category=LineString Property Functions -description=For a LineString value ls, ST_IsClosed() returns 1 if ls is closed (that\nis, its ST_StartPoint() and ST_EndPoint() values are the same) -[ST_ISEMPTY] -declaration=g -category=Geometry Property Functions -description=This function is a placeholder that returns 1 for an empty geometry\ncollection value or 0 otherwise -[ST_ISSIMPLE] -declaration=g -category=Geometry Property Functions -description=Returns 1 if the geometry value g is simple according to the ISO SQL/MM\nPart 3: Spatial standard +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 spatially intersects g2.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-object-shapes.html [ST_ISVALID] declaration=g -category=MBR Functions -description=Returns 1 if the argument is geometrically valid, 0 if the argument is not\ngeometrically valid +category=MBR +description=Checks whether a geometry is valid, as defined by the OGC\nspecification. ST_IsValid() returns 1 if the argument is a valid\ngeometry byte string and is geometrically valid, 0 if the argument is\nnot a valid geometry byte string or is not geometrically valid, NULL if\nthe argument is NULL.\n\nThe only valid empty geometry is represented in the form of an empty\ngeometry collection value. ST_IsValid() returns 1 in this case.\n\nST_IsValid() works only for the cartesian coordinate system and\nrequires a geometry argument with an SRID of 0. An ER_WRONG_ARGUMENTS\nerror occurs otherwise.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-convenience-functions.html [ST_LATFROMGEOHASH] declaration=geohash_str -category=MBR Functions -description=Returns the latitude from a geohash string value, as a double-precision\nnumber in the range [ÔêÆ90, 90] -[ST_LATITUDE] -declaration=p [, new_latitude_val] -category=Point Property Functions -description=With a single argument representing a valid Point object p that has a\ngeographic spatial reference system (SRS), ST_Latitude() returns the\nlatitude value of p as a double-precision number +category=MBR +description=Returns the latitude from a geohash string value, as a DOUBLE value in\nthe range [-90, 90]. The result is NULL if any argument is NULL. An\nerror occurs if the argument is invalid.\n\nThe ST_LatFromGeoHash() decoding function reads no more than 433\ncharacters from the geohash_str argument. That represents the upper\nlimit on information in the internal representation of coordinate\nvalues. Characters past the 433rd are ignored, even if they are\notherwise illegal and produce an error.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-geohash-functions.html [ST_LENGTH] -declaration=ls [, unit] -category=LineString Property Functions -description=Returns a double-precision number indicating the length of the LineString\nor MultiLineString value ls in its associated spatial reference system -[ST_LINEFROMTEXT] -declaration=wkt [, srid [, options]] -category=WKT Functions -description=srid [, options]]) Constructs a LineString value using its WKT\nrepresentation and SRID -[ST_LINEFROMWKB] -declaration=wkb [, srid [, options]] -category=WKB Functions -description=srid [, options]]) Constructs a LineString value using its WKB\nrepresentation and SRID -[ST_LINEINTERPOLATEPOINT] -declaration=ls, fractional_distance -category=GeometryCollection Property Functions -description=This function takes a LineString geometry and a fractional distance in the\nrange [0 -[ST_LINEINTERPOLATEPOINTS] -declaration=ls, fractional_distance -category=GeometryCollection Property Functions -description=This function takes a LineString geometry and a fractional distance in the\nrange (0 +declaration=ls +category=LineString properties +description=Returns a double-precision number indicating the length of the\nLineString value ls in its associated spatial reference.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-linestring-property-functions.html [ST_LONGFROMGEOHASH] declaration=geohash_str -category=MBR Functions -description=Returns the longitude from a geohash string value, as a double-precision\nnumber in the range [ÔêÆ180, 180] -[ST_LONGITUDE] -declaration=p [, new_longitude_val] -category=Point Property Functions -description=With a single argument representing a valid Point object p that has a\ngeographic spatial reference system (SRS), ST_Longitude() returns the\nlongitude value of p as a double-precision number +category=MBR +description=Returns the longitude from a geohash string value, as a DOUBLE value in\nthe range [-180, 180]. The result is NULL if any argument is NULL. An\nerror occurs if the argument is invalid.\n\nThe remarks in the description of ST_LatFromGeoHash() regarding the\nmaximum number of characters processed from the geohash_str argument\nalso apply to ST_LongFromGeoHash().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-geohash-functions.html [ST_MAKEENVELOPE] declaration=pt1, pt2 -category=MBR Functions -description=Returns the rectangle that forms the envelope around two points, as a\nPoint, LineString, or Polygon -[ST_MLINEFROMTEXT] -declaration=wkt [, srid [, options]] -category=WKT Functions -description=ST_MultiLineStringFromText(wkt [, srid [, options]]) Constructs a\nMultiLineString value using its WKT representation and SRID -[ST_MLINEFROMWKB] -declaration=wkb [, srid [, options]] -category=WKB Functions -description=ST_MultiLineStringFromWKB(wkb [, srid [, options]]) Constructs a\nMultiLineString value using its WKB representation and SRID -[ST_MPOINTFROMTEXT] -declaration=wkt [, srid [, options]] -category=WKT Functions -description=[, srid [, options]]) Constructs a MultiPoint value using its WKT\nrepresentation and SRID -[ST_MPOINTFROMWKB] -declaration=wkb [, srid [, options]] -category=WKB Functions -description=srid [, options]]) Constructs a MultiPoint value using its WKB\nrepresentation and SRID -[ST_MPOLYFROMTEXT] -declaration=wkt [, srid [, options]] -category=WKT Functions -description=[, srid [, options]]) Constructs a MultiPolygon value using its WKT\nrepresentation and SRID -[ST_MPOLYFROMWKB] -declaration=wkb [, srid [, options]] -category=WKB Functions -description=[, srid [, options]]) Constructs a MultiPolygon value using its WKB\nrepresentation and SRID -[ST_NUMGEOMETRIES] -declaration=gc -category=GeometryCollection Property Functions -description=Returns the number of geometries in the GeometryCollection value gc -[ST_NUMPOINTS] -declaration=ls -category=LineString Property Functions -description=Returns the number of Point objects in the LineString value ls +category=MBR +description=Returns the rectangle that forms the envelope around two points. The\nreturned geometry is a Point, LineString, or Polygon, or NULL if any\nargument is NULL.\n\nCalculations are done using the cartesian coordinate system rather than\non a sphere, spheroid, or on earth.\n\nGiven two points pt1 and pt2, ST_MakeEnvelope() creates the result\ngeometry on an abstract plane like this:\n\no If pt1 and pt2 are equal, the result is the point pt1.\n\no Otherwise, if (pt1, pt2) is a vertical or horizontal line segment,\n the result is the line segment (pt1, pt2).\n\no Otherwise, the result is a polygon using pt1 and pt2 as diagonal\n points. Either or both of pt1 and pt2 can be vertex points.\n\nThe result geometry has an SRID of 0.\n\nST_MakeEnvelope() requires Point geometry arguments with an SRID of 0.\nAn ER_WRONG_ARGUMENTS error occurs otherwise.\n\nAn ER_GIS_INVALID_DATA occurs if any argument is not a valid geometry\nbyte string, or if any coordinate value of the two points is infinite\n(that is, NaN).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-convenience-functions.html [ST_OVERLAPS] -declaration=g1, g2 -category=Geometry Relation Functions -description=Two geometries spatially overlap if they intersect and their intersection\nresults in a geometry of the same dimension but not equal to either of the\ngiven geometries -[ST_POINTATDISTANCE] -declaration=ls, distance -category=GeometryCollection Property Functions -description=This function takes a LineString geometry and a distance in the range [0 +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 spatially overlaps g2. The term\nspatially overlaps is used if two geometries intersect and their\nintersection results in a geometry of the same dimension but not equal\nto either of the given geometries.\n\nAs of MySQL 5.7.5, this function returns 0 if called with an\ninapplicable geometry argument type combination. For example, it\nreturns 0 if called with geometries of different dimensions or any\nargument is a Point.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-object-shapes.html [ST_POINTFROMGEOHASH] declaration=geohash_str, srid -category=MBR Functions -description=Returns a POINT value containing the decoded geohash value, given a geohash\nstring value -[ST_POINTFROMTEXT] -declaration=wkt [, srid [, options]] -category=WKT Functions -description=Constructs a Point value using its WKT representation and SRID -[ST_POINTFROMWKB] -declaration=wkb [, srid [, options]] -category=WKB Functions -description=Constructs a Point value using its WKB representation and SRID -[ST_POINTN] -declaration=ls, N -category=LineString Property Functions -description=Returns the N-th Point in the Linestring value ls -[ST_POLYFROMTEXT] -declaration=wkt [, srid [, options]] -category=WKT Functions -description=srid [, options]]) Constructs a Polygon value using its WKT representation\nand SRID -[ST_POLYFROMWKB] -declaration=wkb [, srid [, options]] -category=WKB Functions -description=[, options]]) Constructs a Polygon value using its WKB representation and\nSRID +category=MBR +description=Returns a POINT value containing the decoded geohash value, given a\ngeohash string value. The X and Y coordinates of the point are the\nlongitude in the range [-180, 180] and the latitude in the range [-90,\n90], respectively. The srid value is an unsigned 32-bit integer. The\nresult is NULL if any argument is NULL. An error occurs if any argument\nis invalid.\n\nThe remarks in the description of ST_LatFromGeoHash() regarding the\nmaximum number of characters processed from the geohash_str argument\nalso apply to ST_PointFromGeoHash().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-geohash-functions.html [ST_SIMPLIFY] declaration=g, max_distance -category=MBR Functions -description=Simplifies a geometry using the Douglas-Peucker algorithm and returns a\nsimplified value of the same type -[ST_SRID] -declaration=g [, srid] -category=Geometry Property Functions -description=With a single argument representing a valid geometry object g, ST_SRID()\nreturns an integer indicating the ID of the spatial reference system (SRS)\nassociated with g -[ST_STARTPOINT] -declaration=ls -category=LineString Property Functions -description=Returns the Point that is the start point of the LineString value ls -[ST_SWAPXY] -declaration=g -category=WKB Functions -description=Accepts an argument in internal geometry format, swaps the X and Y values\nof each coordinate pair within the geometry, and returns the result +category=MBR +description=Simplifies a geometry using the Douglas-Peucker algorithm and returns a\nsimplified value of the same type, or NULL if any argument is NULL.\n\nThe geometry may be any geometry type, although the Douglas-Peucker\nalgorithm may not actually process every type. A geometry collection is\nprocessed by giving its components one by one to the simplification\nalgorithm, and the returned geometries are put into a geometry\ncollection as result.\n\nThe max_distance argument is the distance (in units of the input\ncoordinates) of a vertex to other segments to be removed. Vertices\nwithin this distance of the simplified linestring are removed. An\nER_WRONG_ARGUMENTS error occurs if the max_distance argument is not\npositive, or is NaN.\n\nAccording to Boost.Geometry, geometries might become invalid as a\nresult of the simplification process, and the process might create\nself-intersections. If you want to check the validity of the result,\npass it to ST_IsValid().\n\nAn ER_GIS_INVALID_DATA error occurs if the geometry argument is not a\nvalid geometry byte string.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-convenience-functions.html [ST_SYMDIFFERENCE] declaration=g1, g2 -category=GeometryCollection Property Functions -description=Returns a geometry that represents the point set symmetric difference of\nthe geometry values g1 and g2, which is defined as: g1 symdifference g2 :=\n(g1 union g2) difference (g1 intersection g2) Or, in function call\nnotation: ST_SymDifference(g1, g2) = ST_Difference(ST_Union(g1, g2),\nST_Intersection(g1, g2)) ST_SymDifference() handles its arguments as\ndescribed in the introduction to this section +category=GeometryCollection properties +description=Returns a geometry that represents the point set symmetric difference\nof the geometry values g1 and g2, which is defined as:\n\ng1 symdifference g2 := (g1 union g2) difference (g1 intersection g2)\n\nOr, in function call notation:\n\nST_SymDifference(g1, g2) = ST_Difference(ST_Union(g1, g2), ST_Intersection(g1, g2))\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-operator-functions.html [ST_TOUCHES] -declaration=g1, g2 -category=Geometry Relation Functions -description=Two geometries spatially touch if their interiors do not intersect, but the\nboundary of one of the geometries intersects either the boundary or the\ninterior of the other -[ST_TRANSFORM] -declaration=g, target_srid -category=GeometryCollection Property Functions -description=Transforms a geometry from one spatial reference system (SRS) to another +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 spatially touches g2. Two\ngeometries spatially touch if the interiors of the geometries do not\nintersect, but the boundary of one of the geometries intersects either\nthe boundary or the interior of the other.\n\nAs of MySQL 5.7.5, this function returns 0 if called with an\ninapplicable geometry argument type combination. For example, it\nreturns 0 if either of the arguments is a Point or MultiPoint.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-object-shapes.html [ST_UNION] declaration=g1, g2 -category=GeometryCollection Property Functions -description=Returns a geometry that represents the point set union of the geometry\nvalues g1 and g2 +category=GeometryCollection properties +description=Returns a geometry that represents the point set union of the geometry\nvalues g1 and g2.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-operator-functions.html [ST_VALIDATE] declaration=g -category=MBR Functions -description=Validates a geometry according to the OGC specification +category=MBR +description=Validates a geometry according to the OGC specification. ST_Validate()\nreturns the geometry if it is a valid geometry byte string and is\ngeometrically valid, NULL if the argument is not a valid geometry byte\nstring or is not geometrically valid or is NULL.\n\nA geometry can be a valid geometry byte string (WKB value plus SRID)\nbut geometrically invalid. For example, this polygon is geometrically\ninvalid: POLYGON((0 0, 0 0, 0 0, 0 0, 0 0))\n\nST_Validate() can be used to filter out invalid geometry data, although\nat a cost. For applications that require more precise results not\ntainted by invalid data, this penalty may be worthwhile.\n\nIf the geometry argument is valid, it is returned as is, except that if\nan input Polygon or MultiPolygon has clockwise rings, those rings are\nreversed before checking for validity. If the geometry is valid, the\nvalue with the reversed rings is returned.\n\nThe only valid empty geometry is represented in the form of an empty\ngeometry collection value. ST_Validate() returns it directly without\nfurther checks in this case.\n\nST_Validate() works only for the cartesian coordinate system and\nrequires a geometry argument with an SRID of 0. An ER_WRONG_ARGUMENTS\nerror occurs otherwise.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-convenience-functions.html [ST_WITHIN] -declaration=g1, g2 -category=Geometry Relation Functions -description=Returns 1 or 0 to indicate whether g1 is spatially within g2 -[ST_X] -declaration=p [, new_x_val] -category=Point Property Functions -description=With a single argument representing a valid Point object p, ST_X() returns\nthe X-coordinate value of p as a double-precision number -[ST_Y] -declaration=p [, new_y_val] -category=Point Property Functions -description=With a single argument representing a valid Point object p, ST_Y() returns\nthe Y-coordinate value of p as a double-precision number +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 is spatially within g2. This\ntests the opposite relationship as ST_Contains().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-object-shapes.html [SUBDATE] declaration=date,INTERVAL expr unit category=Date and Time Functions -description=When invoked with the INTERVAL form of the second argument, SUBDATE() is a\nsynonym for DATE_SUB() +description=When invoked with the INTERVAL form of the second argument, SUBDATE()\nis a synonym for DATE_SUB(). For information on the INTERVAL unit\nargument, see the discussion for DATE_ADD().\n\nmysql> SELECT DATE_SUB('2008-01-02', INTERVAL 31 DAY);\n -> '2007-12-02'\nmysql> SELECT SUBDATE('2008-01-02', INTERVAL 31 DAY);\n -> '2007-12-02'\n\nThe second form enables the use of an integer value for days. In such\ncases, it is interpreted as the number of days to be subtracted from\nthe date or datetime expression expr.\n\nmysql> SELECT SUBDATE('2008-01-02 12:00:00', 31);\n -> '2007-12-02 12:00:00'\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [SUBSTR] declaration=str,pos category=String Functions -description=FROM pos FOR len) SUBSTR() is a synonym for SUBSTRING() +description=FROM pos FOR len)\n\nSUBSTR() is a synonym for SUBSTRING().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [SUBSTRING] declaration=str,pos category=String Functions -description=SUBSTRING(str FROM pos FOR len) The forms without a len argument return a\nsubstring from string str starting at position pos +description=SUBSTRING(str FROM pos FOR len)\n\nThe forms without a len argument return a substring from string str\nstarting at position pos. The forms with a len argument return a\nsubstring len characters long from string str, starting at position\npos. The forms that use FROM are standard SQL syntax. It is also\npossible to use a negative value for pos. In this case, the beginning\nof the substring is pos characters from the end of the string, rather\nthan the beginning. A negative value may be used for pos in any of the\nforms of this function.\n\nFor all forms of SUBSTRING(), the position of the first character in\nthe string from which the substring is to be extracted is reckoned as\n1.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [SUBSTRING_INDEX] declaration=str,delim,count category=String Functions -description=Returns the substring from string str before count occurrences of the\ndelimiter delim +description=Returns the substring from string str before count occurrences of the\ndelimiter delim. If count is positive, everything to the left of the\nfinal delimiter (counting from the left) is returned. If count is\nnegative, everything to the right of the final delimiter (counting from\nthe right) is returned. SUBSTRING_INDEX() performs a case-sensitive\nmatch when searching for delim.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [SUBTIME] declaration=expr1,expr2 category=Date and Time Functions -description=SUBTIME() returns expr1 ÔêÆ expr2 expressed as a value in the same format\nas expr1 +description=SUBTIME() returns expr1 - expr2 expressed as a value in the same format\nas expr1. expr1 is a time or datetime expression, and expr2 is a time\nexpression.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [SUM] declaration=[DISTINCT] expr -category=Aggregate Functions and Modifiers -description=Returns the sum of expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the sum of expr. If the return set has no rows, SUM() returns\nNULL. The DISTINCT keyword can be used to sum only the distinct values\nof expr.\n\nSUM() returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [SYSDATE] declaration=[fsp] category=Date and Time Functions -description=Returns the current date and time as a value in 'YYYY-MM-DD hh:mm:ss' or\nYYYYMMDDhhmmss format, depending on whether the function is used in string\nor numeric context +description=Returns the current date and time as a value in 'YYYY-MM-DD HH:MM:SS'\nor YYYYMMDDHHMMSS format, depending on whether the function is used in\na string or numeric context.\n\nIf the fsp argument is given to specify a fractional seconds precision\nfrom 0 to 6, the return value includes a fractional seconds part of\nthat many digits. Before 5.6.4, any argument is ignored.\n\nSYSDATE() returns the time at which it executes. This differs from the\nbehavior for NOW(), which returns a constant time that indicates the\ntime at which the statement began to execute. (Within a stored function\nor trigger, NOW() returns the time at which the function or triggering\nstatement began to execute.)\n\nmysql> SELECT NOW(), SLEEP(2), NOW();\n+---------------------+----------+---------------------+\n| NOW() | SLEEP(2) | NOW() |\n+---------------------+----------+---------------------+\n| 2006-04-12 13:47:36 | 0 | 2006-04-12 13:47:36 |\n+---------------------+----------+---------------------+\n\nmysql> SELECT SYSDATE(), SLEEP(2), SYSDATE();\n+---------------------+----------+---------------------+\n| SYSDATE() | SLEEP(2) | SYSDATE() |\n+---------------------+----------+---------------------+\n| 2006-04-12 13:47:44 | 0 | 2006-04-12 13:47:46 |\n+---------------------+----------+---------------------+\n\nIn addition, the SET TIMESTAMP statement affects the value returned by\nNOW() but not by SYSDATE(). This means that timestamp settings in the\nbinary log have no effect on invocations of SYSDATE().\n\nBecause SYSDATE() can return different values even within the same\nstatement, and is not affected by SET TIMESTAMP, it is nondeterministic\nand therefore unsafe for replication if statement-based binary logging\nis used. If that is a problem, you can use row-based logging.\n\nAlternatively, you can use the --sysdate-is-now option to cause\nSYSDATE() to be an alias for NOW(). This works if the option is used on\nboth the master and the slave.\n\nThe nondeterministic nature of SYSDATE() also means that indexes cannot\nbe used for evaluating expressions that refer to it.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[SYSTEM_USER] +declaration= +category=Information Functions +description=SYSTEM_USER() is a synonym for USER().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [TAN] declaration=X category=Numeric Functions -description=Returns the tangent of X, where X is given in radians +description=Returns the tangent of X, where X is given in radians.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html +[TEXT] +declaration=M +category=Data Types +description=A TEXT column with a maximum length of 65,535 (216 - 1) characters. The\neffective maximum length is less if the value contains multibyte\ncharacters. Each TEXT value is stored using a 2-byte length prefix that\nindicates the number of bytes in the value.\n\nAn optional length M can be given for this type. If this is done, MySQL\ncreates the column as the smallest TEXT type large enough to hold\nvalues M characters long.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html +[TIME] +declaration=fsp +category=Data Types +description=A time. The range is '-838:59:59.000000' to '838:59:59.000000'. MySQL\ndisplays TIME values in 'HH:MM:SS[.fraction]' format, but permits\nassignment of values to TIME columns using either strings or numbers.\n\nAn optional fsp value in the range from 0 to 6 may be given to specify\nfractional seconds precision. A value of 0 signifies that there is no\nfractional part. If omitted, the default precision is 0.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-overview.html [TIMEDIFF] declaration=expr1,expr2 category=Date and Time Functions -description=TIMEDIFF() returns expr1 ÔêÆ expr2 expressed as a time value +description=TIMEDIFF() returns expr1 - expr2 expressed as a time value. expr1 and\nexpr2 are time or date-and-time expressions, but both must be of the\nsame type.\n\nThe result returned by TIMEDIFF() is limited to the range allowed for\nTIME values. Alternatively, you can use either of the functions\nTIMESTAMPDIFF() and UNIX_TIMESTAMP(), both of which return integers.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[TIMESTAMP] +declaration=fsp +category=Data Types +description=A timestamp. The range is '1970-01-01 00:00:01.000000' UTC to\n'2038-01-19 03:14:07.999999' UTC. TIMESTAMP values are stored as the\nnumber of seconds since the epoch ('1970-01-01 00:00:00' UTC). A\nTIMESTAMP cannot represent the value '1970-01-01 00:00:00' because that\nis equivalent to 0 seconds from the epoch and the value 0 is reserved\nfor representing '0000-00-00 00:00:00', the "zero" TIMESTAMP value.\n\nAn optional fsp value in the range from 0 to 6 may be given to specify\nfractional seconds precision. A value of 0 signifies that there is no\nfractional part. If omitted, the default precision is 0.\n\nThe way the server handles TIMESTAMP definitions depends on the value\nof the explicit_defaults_for_timestamp system variable (see\nhttp://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html).\nBy default, explicit_defaults_for_timestamp is disabled and the server\nhandles TIMESTAMP as follows:\n\nUnless specified otherwise, the first TIMESTAMP column in a table is\ndefined to be automatically set to the date and time of the most recent\nmodification if not explicitly assigned a value. This makes TIMESTAMP\nuseful for recording the timestamp of an INSERT or UPDATE operation.\nYou can also set any TIMESTAMP column to the current date and time by\nassigning it a NULL value, unless it has been defined with the NULL\nattribute to permit NULL values.\n\nAutomatic initialization and updating to the current date and time can\nbe specified using DEFAULT CURRENT_TIMESTAMP and ON UPDATE\nCURRENT_TIMESTAMP column definition clauses. By default, the first\nTIMESTAMP column has these properties, as previously noted. However,\nany TIMESTAMP column in a table can be defined to have these\nproperties.\n\nIf explicit_defaults_for_timestamp is enabled, there is no automatic\nassignment of the DEFAULT CURRENT_TIMESTAMP or ON UPDATE\nCURRENT_TIMESTAMP attributes to any TIMESTAMP column. They must be\nincluded explicitly in the column definition. Also, any TIMESTAMP not\nexplicitly declared as NOT NULL permits NULL values.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-overview.html [TIMESTAMPADD] declaration=unit,interval,datetime_expr category=Date and Time Functions -description=Adds the integer expression interval to the date or datetime expression\ndatetime_expr +description=Adds the integer expression interval to the date or datetime expression\ndatetime_expr. The unit for interval is given by the unit argument,\nwhich should be one of the following values: MICROSECOND\n(microseconds), SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, or\nYEAR.\n\nThe unit value may be specified using one of keywords as shown, or with\na prefix of SQL_TSI_. For example, DAY and SQL_TSI_DAY both are legal.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [TIMESTAMPDIFF] declaration=unit,datetime_expr1,datetime_expr2 category=Date and Time Functions -description=Returns datetime_expr2 - datetime_expr1, where datetime_expr1 and\ndatetime_expr2 are date or datetime expressions +description=Returns datetime_expr2 - datetime_expr1, where datetime_expr1 and\ndatetime_expr2 are date or datetime expressions. One expression may be\na date and the other a datetime; a date value is treated as a datetime\nhaving the time part '00:00:00' where necessary. The unit for the\nresult (an integer) is given by the unit argument. The legal values for\nunit are the same as those listed in the description of the\nTIMESTAMPADD() function.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [TIME_FORMAT] declaration=time,format category=Date and Time Functions -description=This is used like the DATE_FORMAT() function, but the format string may\ncontain format specifiers only for hours, minutes, seconds, and\nmicroseconds +description=This is used like the DATE_FORMAT() function, but the format string may\ncontain format specifiers only for hours, minutes, seconds, and\nmicroseconds. Other specifiers produce a NULL value or 0.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [TIME_TO_SEC] declaration=time category=Date and Time Functions -description=Returns the time argument, converted to seconds -[TO_BASE64] -declaration=str -category=String Functions -description=Converts the string argument to base-64 encoded form and returns the result\nas a character string with the connection character set and collation +description=Returns the time argument, converted to seconds.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[TINYINT] +declaration=M +category=Data Types +description=A very small integer. The signed range is -128 to 127. The unsigned\nrange is 0 to 255.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.html +[TOUCHES] +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 spatially touches g2. Two\ngeometries spatially touch if the interiors of the geometries do not\nintersect, but the boundary of one of the geometries intersects either\nthe boundary or the interior of the other.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html [TO_DAYS] declaration=date category=Date and Time Functions -description=Given a date date, returns a day number (the number of days since year 0) +description=Given a date date, returns a day number (the number of days since year\n0).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [TO_SECONDS] declaration=expr category=Date and Time Functions -description=Given a date or datetime expr, returns the number of seconds since the year\n0 +description=Given a date or datetime expr, returns the number of seconds since the\nyear 0. If expr is not a valid date or datetime value, returns NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [TRIM] declaration=[{BOTH | LEADING | TRAILING} [remstr] FROM] str category=String Functions -description=FROM] str) Returns the string str with all remstr prefixes or suffixes\nremoved +description=FROM] str)\n\nReturns the string str with all remstr prefixes or suffixes removed. If\nnone of the specifiers BOTH, LEADING, or TRAILING is given, BOTH is\nassumed. remstr is optional and, if not specified, spaces are removed.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [TRUNCATE] declaration=X,D category=Numeric Functions -description=Returns the number X, truncated to D decimal places +description=Returns the number X, truncated to D decimal places. If D is 0, the\nresult has no decimal point or fractional part. D can be negative to\ncause D digits left of the decimal point of the value X to become zero.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/mathematical-functions.html [UCASE] declaration=str category=String Functions -description=UCASE() is a synonym for UPPER() +description=UCASE() is a synonym for UPPER().\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [UNCOMPRESS] declaration=string_to_uncompress category=Encryption Functions -description=Uncompresses a string compressed by the COMPRESS() function +description=Uncompresses a string compressed by the COMPRESS() function. If the\nargument is not a compressed value, the result is NULL. This function\nrequires MySQL to have been compiled with a compression library such as\nzlib. Otherwise, the return value is always NULL.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html [UNCOMPRESSED_LENGTH] declaration=compressed_string category=Encryption Functions -description=Returns the length that the compressed string had before being compressed +description=Returns the length that the compressed string had before being\ncompressed.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html [UNHEX] declaration=str category=String Functions -description=For a string argument str, UNHEX(str) interprets each pair of characters in\nthe argument as a hexadecimal number and converts it to the byte\nrepresented by the number +description=For a string argument str, UNHEX(str) interprets each pair of\ncharacters in the argument as a hexadecimal number and converts it to\nthe byte represented by the number. The return value is a binary\nstring.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html [UNIX_TIMESTAMP] -declaration=[date] +declaration= category=Date and Time Functions -description=If UNIX_TIMESTAMP() is called with no date argument, it returns a Unix\ntimestamp representing seconds since '1970-01-01 00:00:00' UTC +description=If called with no argument, returns a Unix timestamp (seconds since\n'1970-01-01 00:00:00' UTC) as an unsigned integer. If UNIX_TIMESTAMP()\nis called with a date argument, it returns the value of the argument as\nseconds since '1970-01-01 00:00:00' UTC. date may be a DATE string, a\nDATETIME string, a TIMESTAMP, or a number in the format YYMMDD or\nYYYYMMDD. The server interprets date as a value in the current time\nzone and converts it to an internal value in UTC. Clients can set their\ntime zone as described in\nhttp://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [UPDATEXML] declaration=xml_target, xpath_expr, new_xml -category=XML -description=This function replaces a single portion of a given fragment of XML markup\nxml_target with a new XML fragment new_xml, and then returns the changed\nXML +category=String Functions +description=This function replaces a single portion of a given fragment of XML\nmarkup xml_target with a new XML fragment new_xml, and then returns the\nchanged XML. The portion of xml_target that is replaced matches an\nXPath expression xpath_expr supplied by the user.\n\nIf no expression matching xpath_expr is found, or if multiple matches\nare found, the function returns the original xml_target XML fragment.\nAll three arguments should be strings.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/xml-functions.html [UPPER] declaration=str category=String Functions -description=Returns the string str with all characters changed to uppercase according\nto the current character set mapping -[UUID_TO_BIN] -declaration=string_uuid +description=Returns the string str with all characters changed to uppercase\naccording to the current character set mapping. The default is latin1\n(cp1252 West European).\n\nmysql> SELECT UPPER('Hej');\n -> 'HEJ'\n\nSee the description of LOWER() for information that also applies to\nUPPER(). This included information about how to perform lettercase\nconversion of binary strings (BINARY, VARBINARY, BLOB) for which these\nfunctions are ineffective, and information about case folding for\nUnicode character sets.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-functions.html +[USER] +declaration= +category=Information Functions +description=Returns the current MySQL user name and host name as a string in the\nutf8 character set.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html +[UTC_DATE] +declaration= +category=Date and Time Functions +description=Returns the current UTC date as a value in 'YYYY-MM-DD' or YYYYMMDD\nformat, depending on whether the function is used in a string or\nnumeric context.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[UTC_TIME] +declaration=[fsp] +category=Date and Time Functions +description=Returns the current UTC time as a value in 'HH:MM:SS' or HHMMSS format,\ndepending on whether the function is used in a string or numeric\ncontext.\n\nIf the fsp argument is given to specify a fractional seconds precision\nfrom 0 to 6, the return value includes a fractional seconds part of\nthat many digits.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[UTC_TIMESTAMP] +declaration=[fsp] +category=Date and Time Functions +description=Returns the current UTC date and time as a value in 'YYYY-MM-DD\nHH:MM:SS' or YYYYMMDDHHMMSS format, depending on whether the function\nis used in a string or numeric context.\n\nIf the fsp argument is given to specify a fractional seconds precision\nfrom 0 to 6, the return value includes a fractional seconds part of\nthat many digits.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html +[UUID] +declaration= +category=Miscellaneous Functions +description=Returns a Universal Unique Identifier (UUID) generated according to\n"DCE 1.1: Remote Procedure Call" (Appendix A) CAE (Common Applications\nEnvironment) Specifications published by The Open Group in October 1997\n(Document Number C706,\nhttp://www.opengroup.org/public/pubs/catalog/c706.htm).\n\nA UUID is designed as a number that is globally unique in space and\ntime. Two calls to UUID() are expected to generate two different\nvalues, even if these calls are performed on two separate computers\nthat are not connected to each other.\n\nA UUID is a 128-bit number represented by a utf8 string of five\nhexadecimal numbers in aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee format:\n\no The first three numbers are generated from a timestamp.\n\no The fourth number preserves temporal uniqueness in case the timestamp\n value loses monotonicity (for example, due to daylight saving time).\n\no The fifth number is an IEEE 802 node number that provides spatial\n uniqueness. A random number is substituted if the latter is not\n available (for example, because the host computer has no Ethernet\n card, or we do not know how to find the hardware address of an\n interface on your operating system). In this case, spatial uniqueness\n cannot be guaranteed. Nevertheless, a collision should have very low\n probability.\n\n Currently, the MAC address of an interface is taken into account only\n on FreeBSD and Linux. On other operating systems, MySQL uses a\n randomly generated 48-bit number.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html +[UUID_SHORT] +declaration= category=Miscellaneous Functions -description=Converts a string UUID to a binary UUID and returns the result +description=Returns a "short" universal identifier as a 64-bit unsigned integer\n(rather than a string-form 128-bit identifier as returned by the UUID()\nfunction).\n\nThe value of UUID_SHORT() is guaranteed to be unique if the following\nconditions hold:\n\no The server_id of the current host is unique among your set of master\n and slave servers\n\no server_id is between 0 and 255\n\no You do not set back your system time for your server between mysqld\n restarts\n\no You do not invoke UUID_SHORT() on average more than 16 million times\n per second between mysqld restarts\n\nThe UUID_SHORT() return value is constructed this way:\n\n (server_id & 255) << 56\n+ (server_startup_time_in_seconds << 24)\n+ incremented_variable++;\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [VALIDATE_PASSWORD_STRENGTH] declaration=str category=Encryption Functions -description=Given an argument representing a plaintext password, this function returns\nan integer to indicate how strong the password is +description=Given an argument representing a cleartext password, this function\nreturns an integer to indicate how strong the password is. The return\nvalue ranges from 0 (weak) to 100 (strong).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html [VALUES] declaration=col_name category=Miscellaneous Functions -description=In an INSERT +description=In an INSERT ... ON DUPLICATE KEY UPDATE statement, you can use the\nVALUES(col_name) function in the UPDATE clause to refer to column\nvalues from the INSERT portion of the statement. In other words,\nVALUES(col_name) in the UPDATE clause refers to the value of col_name\nthat would be inserted, had no duplicate-key conflict occurred. This\nfunction is especially useful in multiple-row inserts. The VALUES()\nfunction is meaningful only in the ON DUPLICATE KEY UPDATE clause of\nINSERT statements and returns NULL otherwise. See\nhttp://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html [VARBINARY] declaration=M category=Data Types -description=The VARBINARY type is similar to the VARCHAR type, but stores binary byte\nstrings rather than nonbinary character strings +description=The VARBINARY type is similar to the VARCHAR type, but stores binary\nbyte strings rather than nonbinary character strings. M represents the\nmaximum column length in bytes.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html +[VARCHAR] +declaration=M +category=Data Types +description=collation_name]\n\nA variable-length string. M represents the maximum column length in\ncharacters. The range of M is 0 to 65,535. The effective maximum length\nof a VARCHAR is subject to the maximum row size (65,535 bytes, which is\nshared among all columns) and the character set used. For example, utf8\ncharacters can require up to three bytes per character, so a VARCHAR\ncolumn that uses the utf8 character set can be declared to be a maximum\nof 21,844 characters. See\nhttp://dev.mysql.com/doc/refman/5.7/en/column-count-limit.html.\n\nMySQL stores VARCHAR values as a 1-byte or 2-byte length prefix plus\ndata. The length prefix indicates the number of bytes in the value. A\nVARCHAR column uses one length byte if values require no more than 255\nbytes, two length bytes if values may require more than 255 bytes.\n\n*Note*: MySQL 5.7 follows the standard SQL specification, and does not\nremove trailing spaces from VARCHAR values.\n\nVARCHAR is shorthand for CHARACTER VARYING. NATIONAL VARCHAR is the\nstandard SQL way to define that a VARCHAR column should use some\npredefined character set. MySQL 4.1 and up uses utf8 as this predefined\ncharacter set.\nhttp://dev.mysql.com/doc/refman/5.7/en/charset-national.html. NVARCHAR\nis shorthand for NATIONAL VARCHAR.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html [VARIANCE] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns the population standard variance of expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the population standard variance of expr. This is an extension\nto standard SQL. The standard SQL function VAR_POP() can be used\ninstead.\n\nVARIANCE() returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [VAR_POP] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns the population standard variance of expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the population standard variance of expr. It considers rows as\nthe whole population, not as a sample, so it has the number of rows as\nthe denominator. You can also use VARIANCE(), which is equivalent but\nis not standard SQL.\n\nVAR_POP() returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html [VAR_SAMP] declaration=expr -category=Aggregate Functions and Modifiers -description=Returns the sample variance of expr +category=Functions and Modifiers for Use with GROUP BY +description=Returns the sample variance of expr. That is, the denominator is the\nnumber of rows minus one.\n\nVAR_SAMP() returns NULL if there were no matching rows.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html +[VERSION] +declaration= +category=Information Functions +description=Returns a string that indicates the MySQL server version. The string\nuses the utf8 character set. The value might have a suffix in addition\nto the version number. See the description of the version system\nvariable in\nhttp://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html [WAIT_FOR_EXECUTED_GTID_SET] declaration=gtid_set[, timeout] -category=GTID -description=Wait until the server has applied all of the transactions whose global\ntransaction identifiers are contained in gtid_set; that is, until the\ncondition GTID_SUBSET(gtid_subset, @@GLOBAL +category=MBR +description=Introduced in MySQL 5.7.5, WAIT_FOR_EXECUTED_GTID_SET() is similar to\nWAIT_UNTIL_SQL_THREAD_AFTER_GTIDS() in that it waits until a server has\nexecuted all of the transactions whose global transaction identifiers\nare contained in gtid_set, or until timeout seconds have elapsed,\nwhichever occurs first. Unlike WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS(),\nWAIT_FOR_EXECUTED_GTID_SET() does not take into account whether the\nslave is running or not, and an error is returned if GTID-based\nreplication is not enabled.\n\nIn addition, WAIT_FOR_EXECUTED_GTID_SET() returns only the state of the\nquery, where 0 represents success, 1 represents timeout, and any other\nfailures return the error message.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gtid-functions.html [WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS] declaration=gtid_set[, timeout][,channel] -category=GTID -description=WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS() is deprecated +category=MBR +description=Wait until the slave SQL thread has executed all of the transactions\nwhose global transaction identifiers are contained in gtid_set (see\nhttp://dev.mysql.com/doc/refman/5.7/en/replication-gtids-concepts.html,\nfor a definition of "GTID sets"), or until timeout seconds have\nelapsed, whichever occurs first. timeout is optional; the default\ntimeout is 0 seconds, in which case the function waits until all of the\ntransactions in the GTID set have been executed.\n\nFor more information, see\nhttp://dev.mysql.com/doc/refman/5.7/en/replication-gtids.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gtid-functions.html [WEEK] declaration=date[,mode] category=Date and Time Functions -description=This function returns the week number for date +description=This function returns the week number for date. The two-argument form\nof WEEK() enables you to specify whether the week starts on Sunday or\nMonday and whether the return value should be in the range from 0 to 53\nor from 1 to 53. If the mode argument is omitted, the value of the\ndefault_week_format system variable is used. See\nhttp://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [WEEKDAY] declaration=date category=Date and Time Functions -description=Returns the weekday index for date (0 = Monday, 1 = Tuesday, +description=Returns the weekday index for date (0 = Monday, 1 = Tuesday, ... 6 =\nSunday).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [WEEKOFYEAR] declaration=date category=Date and Time Functions -description=Returns the calendar week of the date as a number in the range from 1 to 53 +description=Returns the calendar week of the date as a number in the range from 1\nto 53. WEEKOFYEAR() is a compatibility function that is equivalent to\nWEEK(date,3).\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [WEIGHT_STRING] -declaration=str [AS {CHAR|BINARY}(N)] [flags] +declaration=str [AS {CHAR|BINARY}(N category=String Functions -description=This function returns the weight string for the input string +description=levels: N [ASC|DESC|REVERSE] [, N [ASC|DESC|REVERSE]] ...\n\nThis function returns the weight string for the input string. The\nreturn value is a binary string that represents the sorting and\ncomparison value of the string. It has these properties:\n\no If WEIGHT_STRING(str1) = WEIGHT_STRING(str2), then str1 = str2 (str1\n and str2 are considered equal)\n\no If WEIGHT_STRING(str1) < WEIGHT_STRING(str2), then str1 < str2 (str1\n sorts before str2)\n\nWEIGHT_STRING() can be used for testing and debugging of collations,\nespecially if you are adding a new collation. See\nhttp://dev.mysql.com/doc/refman/5.7/en/adding-collation.html.\n\nThe input string, str, is a string expression. If the input is a\nnonbinary (character) string such as a CHAR, VARCHAR, or TEXT value,\nthe return value contains the collation weights for the string. If the\ninput is a binary (byte) string such as a BINARY, VARBINARY, or BLOB\nvalue, the return value is the same as the input (the weight for each\nbyte in a binary string is the byte value). If the input is NULL,\nWEIGHT_STRING() returns NULL.\n\nExamples:\n\nmysql> SET @s = _latin1 'AB' COLLATE latin1_swedish_ci;\nmysql> SELECT @s, HEX(@s), HEX(WEIGHT_STRING(@s));\n+------+---------+------------------------+\n| @s | HEX(@s) | HEX(WEIGHT_STRING(@s)) |\n+------+---------+------------------------+\n| AB | 4142 | 4142 |\n+------+---------+------------------------+\n\nmysql> SET @s = _latin1 'ab' COLLATE latin1_swedish_ci;\nmysql> SELECT @s, HEX(@s), HEX(WEIGHT_STRING(@s));\n+------+---------+------------------------+\n| @s | HEX(@s) | HEX(WEIGHT_STRING(@s)) |\n+------+---------+------------------------+\n| ab | 6162 | 4142 |\n+------+---------+------------------------+\n\nmysql> SET @s = CAST('AB' AS BINARY);\nmysql> SELECT @s, HEX(@s), HEX(WEIGHT_STRING(@s));\n+------+---------+------------------------+\n| @s | HEX(@s) | HEX(WEIGHT_STRING(@s)) |\n+------+---------+------------------------+\n| AB | 4142 | 4142 |\n+------+---------+------------------------+\n\n ... +[WITHIN] +declaration=g1,g2 +category=Geometry relations +description=Returns 1 or 0 to indicate whether g1 is spatially within g2. This\ntests the opposite relationship as Contains().\n\nThis function is deprecated as of MySQL 5.7.6 and will be removed in a\nfuture MySQL release. Use MBRWithin() instead.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html +[X] +declaration=p +category=Point properties +description=Returns the X-coordinate value for the Point object p as a\ndouble-precision number.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-point-property-functions.html +[Y] +declaration=p +category=Point properties +description=Returns the Y-coordinate value for the Point object p as a\ndouble-precision number.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/gis-point-property-functions.html [YEAR] declaration=date category=Date and Time Functions -description=Returns the year for date, in the range 1000 to 9999, or 0 for the "zero"\ndate +description=Returns the year for date, in the range 1000 to 9999, or 0 for the\n"zero" date.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html [YEARWEEK] declaration=date category=Date and Time Functions -description=Returns year and week for a date +description=Returns year and week for a date. The mode argument works exactly like\nthe mode argument to WEEK(). The year in the result may be different\nfrom the year in the date argument for the first and the last week of\nthe year.\n\nURL: http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html \ No newline at end of file diff --git a/out/functions-mysql8.ini b/out/functions-mysql8.ini new file mode 100644 index 000000000..1fbca7787 --- /dev/null +++ b/out/functions-mysql8.ini @@ -0,0 +1,1572 @@ +[ABS] +declaration=X +category=Numeric Functions +description=Returns the absolute value of X, or NULL if X is NULL.\n\nThe result type is derived from the argument type. An implication of\nthis is that ABS(-9223372036854775808) produces an error because the\nresult cannot be stored in a signed BIGINT value.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[ACOS] +declaration=X +category=Numeric Functions +description=Returns the arc cosine of X, that is, the value whose cosine is X.\nReturns NULL if X is not in the range -1 to 1, or if X is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[ADDDATE] +declaration=date,INTERVAL expr unit +category=Date and Time Functions +description=When invoked with the INTERVAL form of the second argument, ADDDATE()\nis a synonym for DATE_ADD(). The related function SUBDATE() is a\nsynonym for DATE_SUB(). For information on the INTERVAL unit argument,\nsee\nhttps://dev.mysql.com/doc/refman/8.3/en/expressions.html#temporal-inter\nvals.\n\nmysql> SELECT DATE_ADD('2008-01-02', INTERVAL 31 DAY);\n -> '2008-02-02'\nmysql> SELECT ADDDATE('2008-01-02', INTERVAL 31 DAY);\n -> '2008-02-02'\n\nWhen invoked with the days form of the second argument, MySQL treats it\nas an integer number of days to be added to expr.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[ADDTIME] +declaration=expr1,expr2 +category=Date and Time Functions +description=ADDTIME() adds expr2 to expr1 and returns the result. expr1 is a time\nor datetime expression, and expr2 is a time expression. Returns NULL if\nexpr1or expr2 is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[AES_DECRYPT] +declaration=crypt_str,key_str[,init_vector][,kdf_name][,salt][,info | iterations] +category=Encryption Functions +description=This function decrypts data using the official AES (Advanced Encryption\nStandard) algorithm. For more information, see the description of\nAES_ENCRYPT().\n\nStatements that use AES_DECRYPT() are unsafe for statement-based\nreplication.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[AES_ENCRYPT] +declaration=str,key_str[,init_vector][,kdf_name][,salt][,info | iterations] +category=Encryption Functions +description=AES_ENCRYPT() and AES_DECRYPT() implement encryption and decryption of\ndata using the official AES (Advanced Encryption Standard) algorithm,\npreviously known as "Rijndael." The AES standard permits various key\nlengths. By default these functions implement AES with a 128-bit key\nlength. Key lengths of 196 or 256 bits can be used, as described later.\nThe key length is a trade off between performance and security.\n\nAES_ENCRYPT() encrypts the string str using the key string key_str, and\nreturns a binary string containing the encrypted output. AES_DECRYPT()\ndecrypts the encrypted string crypt_str using the key string key_str,\nand returns the original (binary) string in hexadecimal format. (To\nobtain the string as plaintext, cast the result to CHAR. Alternatively,\nstart the mysql client with --skip-binary-as-hex to cause all binary\nvalues to be displayed as text.) If either function argument is NULL,\nthe function returns NULL. If AES_DECRYPT() detects invalid data or\nincorrect padding, it returns NULL. However, it is possible for\nAES_DECRYPT() to return a non-NULL value (possibly garbage) if the\ninput data or the key is invalid.\n\nThese functions support the use of a key derivation function (KDF) to\ncreate a cryptographically strong secret key from the information\npassed in key_str. The derived key is used to encrypt and decrypt the\ndata, and it remains in the MySQL Server instance and is not accessible\nto users. Using a KDF is highly recommended, as it provides better\nsecurity than specifying your own premade key or deriving it by a\nsimpler method as you use the function. The functions support HKDF\n(available from OpenSSL 1.1.0), for which you can specify an optional\nsalt and context-specific information to include in the keying\nmaterial, and PBKDF2 (available from OpenSSL 1.0.2), for which you can\nspecify an optional salt and set the number of iterations used to\nproduce the key.\n\nAES_ENCRYPT() and AES_DECRYPT() permit control of the block encryption\nmode. The block_encryption_mode system variable controls the mode for\nblock-based encryption algorithms. Its default value is aes-128-ecb,\nwhich signifies encryption using a key length of 128 bits and ECB mode.\nFor a description of the permitted values of this variable, see\nhttps://dev.mysql.com/doc/refman/8.3/en/server-system-variables.html.\nThe optional init_vector argument is used to provide an initialization\nvector for block encryption modes that require it.\n\nStatements that use AES_ENCRYPT() or AES_DECRYPT() are unsafe for\nstatement-based replication.\n\nIf AES_ENCRYPT() is invoked from within the mysql client, binary\nstrings display using hexadecimal notation, depending on the value of\nthe --binary-as-hex. For more information about that option, see\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql.html.\n\nThe arguments for the AES_ENCRYPT() and AES_DECRYPT() functions are as\n ... +[ANY_VALUE] +declaration=arg +category=Miscellaneous Functions +description=This function is useful for GROUP BY queries when the\nONLY_FULL_GROUP_BY SQL mode is enabled, for cases when MySQL rejects a\nquery that you know is valid for reasons that MySQL cannot determine.\nThe function return value and type are the same as the return value and\ntype of its argument, but the function result is not checked for the\nONLY_FULL_GROUP_BY SQL mode.\n\nFor example, if name is a nonindexed column, the following query fails\nwith ONLY_FULL_GROUP_BY enabled:\n\nmysql> SELECT name, address, MAX(age) FROM t GROUP BY name;\nERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP\nBY clause and contains nonaggregated column 'mydb.t.address' which\nis not functionally dependent on columns in GROUP BY clause; this\nis incompatible with sql_mode=only_full_group_by\n\nThe failure occurs because address is a nonaggregated column that is\nneither named among GROUP BY columns nor functionally dependent on\nthem. As a result, the address value for rows within each name group is\nnondeterministic. There are multiple ways to cause MySQL to accept the\nquery:\n\no Alter the table to make name a primary key or a unique NOT NULL\n column. This enables MySQL to determine that address is functionally\n dependent on name; that is, address is uniquely determined by name.\n (This technique is inapplicable if NULL must be permitted as a valid\n name value.)\n\no Use ANY_VALUE() to refer to address:\n\nSELECT name, ANY_VALUE(address), MAX(age) FROM t GROUP BY name;\n\n In this case, MySQL ignores the nondeterminism of address values\n within each name group and accepts the query. This may be useful if\n you simply do not care which value of a nonaggregated column is\n chosen for each group. ANY_VALUE() is not an aggregate function,\n unlike functions such as SUM() or COUNT(). It simply acts to suppress\n the test for nondeterminism.\n\no Disable ONLY_FULL_GROUP_BY. This is equivalent to using ANY_VALUE()\n with ONLY_FULL_GROUP_BY enabled, as described in the previous item.\n\nANY_VALUE() is also useful if functional dependence exists between\ncolumns but MySQL cannot determine it. The following query is valid\nbecause age is functionally dependent on the grouping column age-1, but\nMySQL cannot tell that and rejects the query with ONLY_FULL_GROUP_BY\nenabled:\n\nSELECT age FROM t GROUP BY age-1;\n\n ... +[ASCII] +declaration=str +category=String Functions +description=Returns the numeric value of the leftmost character of the string str.\nReturns 0 if str is the empty string. Returns NULL if str is NULL.\nASCII() works for 8-bit characters.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[ASIN] +declaration=X +category=Numeric Functions +description=Returns the arc sine of X, that is, the value whose sine is X. Returns\nNULL if X is not in the range -1 to 1, or if X is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[ASYMMETRIC_DECRYPT] +declaration=algorithm, data_str, priv_key_str +category=Enterprise Encryption Functions +description=Decrypts an encrypted string using the given algorithm and key string,\nand returns the resulting plaintext as a binary string. If decryption\nfails, the result is NULL.\n\nFor the legacy version of this function in use before MySQL 8.0.29, see\nhttps://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions\n-legacy.html.\n\nBy default, the component_enterprise_encryption function assumes that\nencrypted text uses the RSAES-OAEP padding scheme. The function\nsupports decryption for content encrypted by the legacy openssl_udf\nshared library functions if the system variable\nenterprise_encryption.rsa_support_legacy_padding is set to ON (the\ndefault is OFF). When ON is set, the function also supports the\nRSAES-PKCS1-v1_5 padding scheme, as used by the legacy openssl_udf\nshared library functions. When OFF is set, content encrypted by the\nlegacy functions cannot be decrypted, and the function returns null\noutput for such content.\n\nalgorithm is the encryption algorithm used to create the key. The\nsupported algorithm value is 'RSA'.\n\ndata_str is the encrypted string to decrypt, which was encrypted with\nasymmetric_encrypt().\n\npriv_key_str is a valid PEM encoded RSA private key. For successful\ndecryption, the key string must correspond to the public key string\nused with asymmetric_encrypt() to produce the encrypted string. The\nasymmetric_encrypt() component function only supports encryption using\na public key, so decryption takes place with the corresponding private\nkey.\n\nFor a usage example, see the description of asymmetric_encrypt().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html +[ASYMMETRIC_DERIVE] +declaration=pub_key_str, priv_key_str +category=Enterprise Encryption Functions +description=Derives a symmetric key using the private key of one party and the\npublic key of another, and returns the resulting key as a binary\nstring. If key derivation fails, the result is NULL.\n\npub_key_str and priv_key_str are valid PEM encoded key strings that\nwere created using the DH algorithm.\n\nSuppose that you have two pairs of public and private keys:\n\nSET @dhp = create_dh_parameters(1024);\nSET @priv1 = create_asymmetric_priv_key('DH', @dhp);\nSET @pub1 = create_asymmetric_pub_key('DH', @priv1);\nSET @priv2 = create_asymmetric_priv_key('DH', @dhp);\nSET @pub2 = create_asymmetric_pub_key('DH', @priv2);\n\nSuppose further that you use the private key from one pair and the\npublic key from the other pair to create a symmetric key string. Then\nthis symmetric key identity relationship holds:\n\nasymmetric_derive(@pub1, @priv2) = asymmetric_derive(@pub2, @priv1)\n\nThis example requires DH private/public keys as inputs, created using a\nshared symmetric secret. Create the secret by passing the key length to\ncreate_dh_parameters(), then pass the secret as the "key length" to\ncreate_asymmetric_priv_key().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions-legacy.html +[ASYMMETRIC_ENCRYPT] +declaration=algorithm, data_str, pub_key_str +category=Enterprise Encryption Functions +description=Encrypts a string using the given algorithm and key string, and returns\nthe resulting ciphertext as a binary string. If encryption fails, the\nresult is NULL.\n\nFor the legacy version of this function in use before MySQL 8.0.29, see\nhttps://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions\n-legacy.html.\n\nalgorithm is the encryption algorithm used to create the key. The\nsupported algorithm value is 'RSA'.\n\ndata_str is the string to encrypt. The length of this string cannot be\ngreater than the key string length in bytes, minus 42 (to account for\nthe padding).\n\npub_key_str is a valid PEM encoded RSA public key. The\nasymmetric_encrypt() component function only supports encryption using\na public key.\n\nTo recover the original unencrypted string, pass the encrypted string\nto asymmetric_decrypt(), along with the other part of the key pair used\nfor encryption, as in the following example:\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html +[ASYMMETRIC_SIGN] +declaration=algorithm, text, priv_key_str, digest_type +category=Enterprise Encryption Functions +description=Signs a digest string or data string using a private key, and returns\nthe signature as a binary string. If signing fails, the result is NULL.\n\nFor the legacy version of this function in use before MySQL 8.0.29, see\nhttps://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions\n-legacy.html.\n\nalgorithm is the encryption algorithm used to create the key. The\nsupported algorithm value is 'RSA'.\n\ntext is a data string or digest string. The function accepts digests\nbut does not require them, as it is also capable of handling data\nstrings of an arbitrary length. A digest string can be generated by\ncalling create_digest().\n\npriv_key_str is the private key string to use for signing the digest\nstring. It must be a valid PEM encoded RSA private key.\n\ndigest_type is the algorithm to be used to sign the data. The supported\ndigest_type values are 'SHA224', 'SHA256', 'SHA384', and 'SHA512' when\nOpenSSL 1.0.1 is in use. If OpenSSL 1.1.1 is in use, the additional\ndigest_type values 'SHA3-224', 'SHA3-256', 'SHA3-384', and 'SHA3-512'\nare available.\n\nFor a usage example, see the description of asymmetric_verify().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html +[ASYMMETRIC_VERIFY] +declaration=algorithm, text, sig_str, pub_key_str, digest_type +category=Enterprise Encryption Functions +description=Verifies whether the signature string matches the digest string, and\nreturns 1 or 0 to indicate whether verification succeeded or failed. If\nverification fails, the result is NULL.\n\nFor the legacy version of this function in use before MySQL 8.0.29, see\nhttps://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions\n-legacy.html.\n\nBy default, the component_enterprise_encryption function assumes that\nsignatures use the RSASSA-PSS signature scheme. The function supports\nverification for signatures produced by the legacy openssl_udf shared\nlibrary functions if the system variable\nenterprise_encryption.rsa_support_legacy_padding is set to ON (the\ndefault is OFF). When ON is set, the function also supports the\nRSASSA-PKCS1-v1_5 signature scheme, as used by the legacy openssl_udf\nshared library functions. When OFF is set, signatures produced by the\nlegacy functions cannot be verified, and the function returns null\noutput for such content.\n\nalgorithm is the encryption algorithm used to create the key. The\nsupported algorithm value is 'RSA'.\n\ntext is a data string or digest string. The component function accepts\ndigests but does not require them, as it is also capable of handling\ndata strings of an arbitrary length. A digest string can be generated\nby calling create_digest().\n\nsig_str is the signature string to be verified. A signature string can\nbe generated by calling asymmetric_sign().\n\npub_key_str is the public key string of the signer. It corresponds to\nthe private key passed to asymmetric_sign() to generate the signature\nstring. It must be a valid PEM encoded RSA public key.\n\ndigest_type is the algorithm that was used to sign the data. The\nsupported digest_type values are 'SHA224', 'SHA256', 'SHA384', and\n'SHA512' when OpenSSL 1.0.1 is in use. If OpenSSL 1.1.1 is in use, the\nadditional digest_type values 'SHA3-224', 'SHA3-256', 'SHA3-384', and\n'SHA3-512' are available.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html +[ATAN] +declaration=X +category=Numeric Functions +description=Returns the arc tangent of X, that is, the value whose tangent is X.\nReturns NULL if X is NULL\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[ATAN2] +declaration=Y,X +category=Numeric Functions +description=Returns the arc tangent of the two variables X and Y. It is similar to\ncalculating the arc tangent of Y / X, except that the signs of both\narguments are used to determine the quadrant of the result. Returns\nNULL if X or Y is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[AVG] +declaration=[DISTINCT] expr +category=Aggregate Functions and Modifiers +description=Returns the average value of expr. The DISTINCT option can be used to\nreturn the average of the distinct values of expr.\n\nIf there are no matching rows, AVG() returns NULL. The function also\nreturns NULL if expr is NULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html; it\ncannot be used with DISTINCT.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[BENCHMARK] +declaration=count,expr +category=Information Functions +description=The BENCHMARK() function executes the expression expr repeatedly count\ntimes. It may be used to time how quickly MySQL processes the\nexpression. The result value is 0, or NULL for inappropriate arguments\nsuch as a NULL or negative repeat count.\n\nThe intended use is from within the mysql client, which reports query\nexecution times:\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[BIGINT] +declaration=M +category=Data Types +description=A large integer. The signed range is -9223372036854775808 to\n9223372036854775807. The unsigned range is 0 to 18446744073709551615.\n\nSERIAL is an alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[BIN] +declaration=N +category=String Functions +description=Returns a string representation of the binary value of N, where N is a\nlonglong (BIGINT) number. This is equivalent to CONV(N,10,2). Returns\nNULL if N is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[BINARY] +declaration=M +category=Data Types +description=The BINARY type is similar to the CHAR type, but stores binary byte\nstrings rather than nonbinary character strings. An optional length M\nrepresents the column length in bytes. If omitted, M defaults to 1.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html +[BIN_TO_UUID] +declaration=binary_uuid +category=Miscellaneous Functions +description=BIN_TO_UUID() is the inverse of UUID_TO_BIN(). It converts a binary\nUUID to a string UUID and returns the result. The binary value should\nbe a UUID as a VARBINARY(16) value. The return value is a string of\nfive hexadecimal numbers separated by dashes. (For details about this\nformat, see the UUID() function description.) If the UUID argument is\nNULL, the return value is NULL. If any argument is invalid, an error\noccurs.\n\nBIN_TO_UUID() takes one or two arguments:\n\no The one-argument form takes a binary UUID value. The UUID value is\n assumed not to have its time-low and time-high parts swapped. The\n string result is in the same order as the binary argument.\n\no The two-argument form takes a binary UUID value and a swap-flag\n value:\n\n o If swap_flag is 0, the two-argument form is equivalent to the\n one-argument form. The string result is in the same order as the\n binary argument.\n\n o If swap_flag is 1, the UUID value is assumed to have its time-low\n and time-high parts swapped. These parts are swapped back to their\n original position in the result value.\n\nFor usage examples and information about time-part swapping, see the\nUUID_TO_BIN() function description.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[BIT] +declaration=M +category=Data Types +description=A bit-value type. M indicates the number of bits per value, from 1 to\n64. The default is 1 if M is omitted.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[BIT_AND] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns the bitwise AND of all bits in expr.\n\nThe result type depends on whether the function argument values are\nevaluated as binary strings or numbers:\n\no Binary-string evaluation occurs when the argument values have a\n binary string type, and the argument is not a hexadecimal literal,\n bit literal, or NULL literal. Numeric evaluation occurs otherwise,\n with argument value conversion to unsigned 64-bit integers as\n necessary.\n\no Binary-string evaluation produces a binary string of the same length\n as the argument values. If argument values have unequal lengths, an\n ER_INVALID_BITWISE_OPERANDS_SIZE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_invalid_bitwise_operands_size) error occurs. If the\n argument size exceeds 511 bytes, an\n ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_invalid_bitwise_aggregate_operands_size) error occurs.\n Numeric evaluation produces an unsigned 64-bit integer.\n\nIf there are no matching rows, BIT_AND() returns a neutral value (all\nbits set to 1) having the same length as the argument values.\n\nNULL values do not affect the result unless all values are NULL. In\nthat case, the result is a neutral value having the same length as the\nargument values.\n\nFor more information discussion about argument evaluation and result\ntypes, see the introductory discussion in\nhttps://dev.mysql.com/doc/refman/8.3/en/bit-functions.html.\n\nIf BIT_AND() is invoked from within the mysql client, binary string\nresults display using hexadecimal notation, depending on the value of\nthe --binary-as-hex. For more information about that option, see\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql.html.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[BIT_LENGTH] +declaration=str +category=String Functions +description=Returns the length of the string str in bits. Returns NULL if str is\nNULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[BIT_OR] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns the bitwise OR of all bits in expr.\n\nThe result type depends on whether the function argument values are\nevaluated as binary strings or numbers:\n\no Binary-string evaluation occurs when the argument values have a\n binary string type, and the argument is not a hexadecimal literal,\n bit literal, or NULL literal. Numeric evaluation occurs otherwise,\n with argument value conversion to unsigned 64-bit integers as\n necessary.\n\no Binary-string evaluation produces a binary string of the same length\n as the argument values. If argument values have unequal lengths, an\n ER_INVALID_BITWISE_OPERANDS_SIZE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_invalid_bitwise_operands_size) error occurs. If the\n argument size exceeds 511 bytes, an\n ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_invalid_bitwise_aggregate_operands_size) error occurs.\n Numeric evaluation produces an unsigned 64-bit integer.\n\nIf there are no matching rows, BIT_OR() returns a neutral value (all\nbits set to 0) having the same length as the argument values.\n\nNULL values do not affect the result unless all values are NULL. In\nthat case, the result is a neutral value having the same length as the\nargument values.\n\nFor more information discussion about argument evaluation and result\ntypes, see the introductory discussion in\nhttps://dev.mysql.com/doc/refman/8.3/en/bit-functions.html.\n\nIf BIT_OR() is invoked from within the mysql client, binary string\nresults display using hexadecimal notation, depending on the value of\nthe --binary-as-hex. For more information about that option, see\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql.html.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[BIT_XOR] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns the bitwise XOR of all bits in expr.\n\nThe result type depends on whether the function argument values are\nevaluated as binary strings or numbers:\n\no Binary-string evaluation occurs when the argument values have a\n binary string type, and the argument is not a hexadecimal literal,\n bit literal, or NULL literal. Numeric evaluation occurs otherwise,\n with argument value conversion to unsigned 64-bit integers as\n necessary.\n\no Binary-string evaluation produces a binary string of the same length\n as the argument values. If argument values have unequal lengths, an\n ER_INVALID_BITWISE_OPERANDS_SIZE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_invalid_bitwise_operands_size) error occurs. If the\n argument size exceeds 511 bytes, an\n ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_invalid_bitwise_aggregate_operands_size) error occurs.\n Numeric evaluation produces an unsigned 64-bit integer.\n\nIf there are no matching rows, BIT_XOR() returns a neutral value (all\nbits set to 0) having the same length as the argument values.\n\nNULL values do not affect the result unless all values are NULL. In\nthat case, the result is a neutral value having the same length as the\nargument values.\n\nFor more information discussion about argument evaluation and result\ntypes, see the introductory discussion in\nhttps://dev.mysql.com/doc/refman/8.3/en/bit-functions.html.\n\nIf BIT_XOR() is invoked from within the mysql client, binary string\nresults display using hexadecimal notation, depending on the value of\nthe --binary-as-hex. For more information about that option, see\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql.html.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[BLOB] +declaration=M +category=Data Types +description=A BLOB column with a maximum length of 65,535 (216 − 1) bytes. Each\nBLOB value is stored using a 2-byte length prefix that indicates the\nnumber of bytes in the value.\n\nAn optional length M can be given for this type. If this is done, MySQL\ncreates the column as the smallest BLOB type large enough to hold\nvalues M bytes long.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html +[CAST] +declaration=expr AS type [ARRAY] +category=Cast Functions and Operators +description=CAST(timestamp_value AT TIME ZONE timezone_specifier AS\nDATETIME[(precision)])\n\ntimezone_specifier: [INTERVAL] '+00:00' | 'UTC'\n\nWith CAST(expr AS type syntax, the CAST() function takes an expression\nof any type and produces a result value of the specified type. This\noperation may also be expressed as CONVERT(expr, type), which is\nequivalent. If expr is NULL, CAST() returns NULL.\n\nThese type values are permitted:\n\no BINARY[(N)]\n\n Produces a string with the VARBINARY data type, except that when the\n expression expr is empty (zero length), the result type is BINARY(0).\n If the optional length N is given, BINARY(N) causes the cast to use\n no more than N bytes of the argument. Values shorter than N bytes are\n padded with 0x00 bytes to a length of N. If the optional length N is\n not given, MySQL calculates the maximum length from the expression.\n If the supplied or calculated length is greater than an internal\n threshold, the result type is BLOB. If the length is still too long,\n the result type is LONGBLOB.\n\n For a description of how casting to BINARY affects comparisons, see\n https://dev.mysql.com/doc/refman/8.3/en/binary-varbinary.html.\n\no CHAR[(N)] [charset_info]\n\n Produces a string with the VARCHAR data type, unless the expression\n expr is empty (zero length), in which case the result type is\n CHAR(0). If the optional length N is given, CHAR(N) causes the cast\n to use no more than N characters of the argument. No padding occurs\n for values shorter than N characters. If the optional length N is not\n given, MySQL calculates the maximum length from the expression. If\n the supplied or calculated length is greater than an internal\n threshold, the result type is TEXT. If the length is still too long,\n the result type is LONGTEXT.\n\n With no charset_info clause, CHAR produces a string with the default\n character set. To specify the character set explicitly, these\n charset_info values are permitted:\n\n o CHARACTER SET charset_name: Produces a string with the given\n character set.\n\n o ASCII: Shorthand for CHARACTER SET latin1.\n\n o UNICODE: Shorthand for CHARACTER SET ucs2.\n\n ... +[CEIL] +declaration=X +category=Numeric Functions +description=CEIL() is a synonym for CEILING().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[CEILING] +declaration=X +category=Numeric Functions +description=Returns the smallest integer value not less than X. Returns NULL if X\nis NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[CHAR] +declaration=M +category=Data Types +description=collation_name]\n\nA fixed-length string that is always right-padded with spaces to the\nspecified length when stored. M represents the column length in\ncharacters. The range of M is 0 to 255. If M is omitted, the length is\n1.\n\n*Note*:\n\nTrailing spaces are removed when CHAR values are retrieved unless the\nPAD_CHAR_TO_FULL_LENGTH SQL mode is enabled.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html +[CHARACTER_LENGTH] +declaration=str +category=String Functions +description=CHARACTER_LENGTH() is a synonym for CHAR_LENGTH().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[CHARSET] +declaration=str +category=Information Functions +description=Returns the character set of the string argument, or NULL if the\nargument is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[CHAR_LENGTH] +declaration=str +category=String Functions +description=Returns the length of the string str, measured in code points. A\nmultibyte character counts as a single code point. This means that, for\na string containing two 3-byte characters, LENGTH() returns 6, whereas\nCHAR_LENGTH() returns 2, as shown here:\n\nmysql> SET @dolphin:='海豚';\nQuery OK, 0 rows affected (0.01 sec)\n\nmysql> SELECT LENGTH(@dolphin), CHAR_LENGTH(@dolphin);\n+------------------+-----------------------+\n| LENGTH(@dolphin) | CHAR_LENGTH(@dolphin) |\n+------------------+-----------------------+\n| 6 | 2 |\n+------------------+-----------------------+\n1 row in set (0.00 sec)\n\nCHAR_LENGTH() returns NULL if str is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[COALESCE] +declaration=value,... +category=Comparison Operators +description=Returns the first non-NULL value in the list, or NULL if there are no\nnon-NULL values.\n\nThe return type of COALESCE() is the aggregated type of the argument\ntypes.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html +[COERCIBILITY] +declaration=str +category=Information Functions +description=Returns the collation coercibility value of the string argument.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[COLLATION] +declaration=str +category=Information Functions +description=Returns the collation of the string argument.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[COMPRESS] +declaration=string_to_compress +category=Encryption Functions +description=Compresses a string and returns the result as a binary string. This\nfunction requires MySQL to have been compiled with a compression\nlibrary such as zlib. Otherwise, the return value is always NULL. The\nreturn value is also NULL if string_to_compress is NULL. The compressed\nstring can be uncompressed with UNCOMPRESS().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[CONCAT] +declaration=str1,str2,... +category=String Functions +description=Returns the string that results from concatenating the arguments. May\nhave one or more arguments. If all arguments are nonbinary strings, the\nresult is a nonbinary string. If the arguments include any binary\nstrings, the result is a binary string. A numeric argument is converted\nto its equivalent nonbinary string form.\n\nCONCAT() returns NULL if any argument is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[CONCAT_WS] +declaration=separator,str1,str2,... +category=String Functions +description=CONCAT_WS() stands for Concatenate With Separator and is a special form\nof CONCAT(). The first argument is the separator for the rest of the\narguments. The separator is added between the strings to be\nconcatenated. The separator can be a string, as can the rest of the\narguments. If the separator is NULL, the result is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[CONNECTION_ID] +declaration= +category=Information Functions +description=Returns the connection ID (thread ID) for the connection. Every\nconnection has an ID that is unique among the set of currently\nconnected clients.\n\nThe value returned by CONNECTION_ID() is the same type of value as\ndisplayed in the ID column of the Information Schema PROCESSLIST table,\nthe Id column of SHOW PROCESSLIST output, and the PROCESSLIST_ID column\nof the Performance Schema threads table.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[CONV] +declaration=N,from_base,to_base +category=Numeric Functions +description=Converts numbers between different number bases. Returns a string\nrepresentation of the number N, converted from base from_base to base\nto_base. Returns NULL if any argument is NULL. The argument N is\ninterpreted as an integer, but may be specified as an integer or a\nstring. The minimum base is 2 and the maximum base is 36. If from_base\nis a negative number, N is regarded as a signed number. Otherwise, N is\ntreated as unsigned. CONV() works with 64-bit precision.\n\nCONV() returns NULL if any of its arguments are NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[CONVERT] +declaration=expr USING transcoding_name +category=Cast Functions and Operators +description=CONVERT(expr,type)\n\nCONVERT(expr USING transcoding_name) is standard SQL syntax. The\nnon-USING form of CONVERT() is ODBC syntax. Regardless of the syntax\nused, the function returns NULL if expr is NULL.\n\nCONVERT(expr USING transcoding_name) converts data between different\ncharacter sets. In MySQL, transcoding names are the same as the\ncorresponding character set names. For example, this statement converts\nthe string 'abc' in the default character set to the corresponding\nstring in the utf8mb4 character set:\n\nSELECT CONVERT('abc' USING utf8mb4);\n\nCONVERT(expr, type) syntax (without USING) takes an expression and a\ntype value specifying a result type, and produces a result value of the\nspecified type. This operation may also be expressed as CAST(expr AS\ntype), which is equivalent. For more information, see the description\nof CAST().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/cast-functions.html +[CONVERT_TZ] +declaration=dt,from_tz,to_tz +category=Date and Time Functions +description=CONVERT_TZ() converts a datetime value dt from the time zone given by\nfrom_tz to the time zone given by to_tz and returns the resulting\nvalue. Time zones are specified as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/time-zone-support.html. This\nfunction returns NULL if any of the arguments are invalid, or if any of\nthem are NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[COS] +declaration=X +category=Numeric Functions +description=Returns the cosine of X, where X is given in radians. Returns NULL if X\nis NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[COT] +declaration=X +category=Numeric Functions +description=Returns the cotangent of X. Returns NULL if X is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[COUNT] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns a count of the number of non-NULL values of expr in the rows\nretrieved by a SELECT statement. The result is a BIGINT value.\n\nIf there are no matching rows, COUNT() returns 0. COUNT(NULL) returns\n0.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[CRC32] +declaration=expr +category=Numeric Functions +description=Computes a cyclic redundancy check value and returns a 32-bit unsigned\nvalue. The result is NULL if the argument is NULL. The argument is\nexpected to be a string and (if possible) is treated as one if it is\nnot.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[CREATE_ASYMMETRIC_PRIV_KEY] +declaration=algorithm, key_length +category=Enterprise Encryption Functions +description=Creates a private key using the given algorithm and key length, and\nreturns the key as a binary string in PEM format. The key is in PKCS #8\nformat. If key generation fails, the result is NULL.\n\nFor the legacy version of this function in use before MySQL 8.0.29, see\nhttps://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions\n-legacy.html.\n\nalgorithm is the encryption algorithm used to create the key. The\nsupported algorithm value is 'RSA'.\n\nkey_length is the key length in bits. If you exceed the maximum allowed\nkey length or specify less than the minimum, key generation fails and\nthe result is null output. The minimum allowed key length in bits is\n2048. The maximum allowed key length is the value of the\nenterprise_encryption.maximum_rsa_key_size system variable, which\ndefaults to 4096. It has a maximum setting of 16384, which is the\nmaximum key length allowed for the RSA algorithm. See\nhttps://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-configuri\nng.html.\n\n*Note*:\n\nGenerating longer keys can consume significant CPU resources. Limiting\nthe key length using the enterprise_encryption.maximum_rsa_key_size\nsystem variable lets you provide adequate security for your\nrequirements while balancing this with resource usage.\n\nThis example creates a 2048-bit RSA private key, then derives a public\nkey from the private key:\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html +[CREATE_ASYMMETRIC_PUB_KEY] +declaration=algorithm, priv_key_str +category=Enterprise Encryption Functions +description=Derives a public key from the given private key using the given\nalgorithm, and returns the key as a binary string in PEM format. The\nkey is in PKCS #8 format. If key derivation fails, the result is NULL.\n\nFor the legacy version of this function in use before MySQL 8.0.29, see\nhttps://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions\n-legacy.html.\n\nalgorithm is the encryption algorithm used to create the key. The\nsupported algorithm value is 'RSA'.\n\npriv_key_str is a valid PEM encoded RSA private key.\n\nFor a usage example, see the description of\ncreate_asymmetric_priv_key().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html +[CREATE_DH_PARAMETERS] +declaration=key_len +category=Enterprise Encryption Functions +description=Creates a shared secret for generating a DH private/public key pair and\nreturns a binary string that can be passed to\ncreate_asymmetric_priv_key(). If secret generation fails, the result is\nNULL.\n\nkey_len is the key length. The minimum and maximum key lengths in bits\nare 1,024 and 10,000. These key-length limits are constraints imposed\nby OpenSSL. Server administrators can impose additional limits on\nmaximum key length by setting the MYSQL_OPENSSL_UDF_RSA_BITS_THRESHOLD,\nMYSQL_OPENSSL_UDF_DSA_BITS_THRESHOLD, and\nMYSQL_OPENSSL_UDF_DH_BITS_THRESHOLD environment variables. See\nhttps://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-configuri\nng.html.\n\nFor an example showing how to use the return value for generating\nsymmetric keys, see the description of asymmetric_derive().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions-legacy.html +[CREATE_DIGEST] +declaration=digest_type, str +category=Enterprise Encryption Functions +description=Creates a digest from the given string using the given digest type, and\nreturns the digest as a binary string. If digest generation fails, the\nresult is NULL.\n\nFor the legacy version of this function in use before MySQL 8.0.29, see\nhttps://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions\n-legacy.html.\n\nThe resulting digest string is suitable for use with asymmetric_sign()\nand asymmetric_verify(). The component versions of these functions\naccept digests but do not require them, as they are capable of handling\ndata of an arbitrary length.\n\ndigest_type is the digest algorithm to be used to generate the digest\nstring. The supported digest_type values are 'SHA224', 'SHA256',\n'SHA384', and 'SHA512' when OpenSSL 1.0.1 is in use. If OpenSSL 1.1.1\nis in use, the additional digest_type values 'SHA3-224', 'SHA3-256',\n'SHA3-384', and 'SHA3-512' are available.\n\nstr is the non-null data string for which the digest is to be\ngenerated.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html +[CUME_DIST] +declaration= +category=Window Functions +description=Returns the cumulative distribution of a value within a group of\nvalues; that is, the percentage of partition values less than or equal\nto the value in the current row. This represents the number of rows\npreceding or peer with the current row in the window ordering of the\nwindow partition divided by the total number of rows in the window\npartition. Return values range from 0 to 1.\n\nThis function should be used with ORDER BY to sort partition rows into\nthe desired order. Without ORDER BY, all rows are peers and have value\nN/N = 1, where N is the partition size.\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[CURDATE] +declaration= +category=Date and Time Functions +description=Returns the current date as a value in 'YYYY-MM-DD' or YYYYMMDD format,\ndepending on whether the function is used in string or numeric context.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[CURRENT_DATE] +declaration= +category=Date and Time Functions +description=CURRENT_DATE and CURRENT_DATE() are synonyms for CURDATE().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[CURRENT_ROLE] +declaration= +category=Information Functions +description=Returns a utf8mb3 string containing the current active roles for the\ncurrent session, separated by commas, or NONE if there are none. The\nvalue reflects the setting of the sql_quote_show_create system\nvariable.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[CURRENT_TIME] +declaration=[fsp] +category=Date and Time Functions +description=CURRENT_TIME and CURRENT_TIME() are synonyms for CURTIME().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[CURRENT_TIMESTAMP] +declaration=[fsp] +category=Date and Time Functions +description=CURRENT_TIMESTAMP and CURRENT_TIMESTAMP() are synonyms for NOW().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[CURRENT_USER] +declaration= +category=Information Functions +description=Returns the user name and host name combination for the MySQL account\nthat the server used to authenticate the current client. This account\ndetermines your access privileges. The return value is a string in the\nutf8mb3 character set.\n\nThe value of CURRENT_USER() can differ from the value of USER().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[CURTIME] +declaration=[fsp] +category=Date and Time Functions +description=Returns the current time as a value in 'hh:mm:ss' or hhmmss format,\ndepending on whether the function is used in string or numeric context.\nThe value is expressed in the session time zone.\n\nIf the fsp argument is given to specify a fractional seconds precision\nfrom 0 to 6, the return value includes a fractional seconds part of\nthat many digits.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[DATABASE] +declaration= +category=Information Functions +description=Returns the default (current) database name as a string in the utf8mb3\ncharacter set. If there is no default database, DATABASE() returns\nNULL. Within a stored routine, the default database is the database\nthat the routine is associated with, which is not necessarily the same\nas the database that is the default in the calling context.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[DATEDIFF] +declaration=expr1,expr2 +category=Date and Time Functions +description=DATEDIFF() returns expr1 − expr2 expressed as a value in days from\none date to the other. expr1 and expr2 are date or date-and-time\nexpressions. Only the date parts of the values are used in the\ncalculation.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[DATETIME] +declaration=fsp +category=Data Types +description=A date and time combination. The supported range is '1000-01-01\n00:00:00.000000' to '9999-12-31 23:59:59.499999'. MySQL displays\nDATETIME values in 'YYYY-MM-DD hh:mm:ss[.fraction]' format, but permits\nassignment of values to DATETIME columns using either strings or\nnumbers.\n\nAn optional fsp value in the range from 0 to 6 may be given to specify\nfractional seconds precision. A value of 0 signifies that there is no\nfractional part. If omitted, the default precision is 0.\n\nAutomatic initialization and updating to the current date and time for\nDATETIME columns can be specified using DEFAULT and ON UPDATE column\ndefinition clauses, as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/timestamp-initialization.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-type-syntax.html +[DATE_ADD] +declaration=date,INTERVAL expr unit +category=Date and Time Functions +description=These functions perform date arithmetic. The date argument specifies\nthe starting date or datetime value. expr is an expression specifying\nthe interval value to be added or subtracted from the starting date.\nexpr is evaluated as a string; it may start with a - for negative\nintervals. unit is a keyword indicating the units in which the\nexpression should be interpreted.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[DATE_FORMAT] +declaration=date,format +category=Date and Time Functions +description=Formats the date value according to the format string. If either\nargument is NULL, the function returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[DATE_SUB] +declaration=date,INTERVAL expr unit +category=Date and Time Functions +description=See the description for DATE_ADD().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[DAY] +declaration=date +category=Date and Time Functions +description=DAY() is a synonym for DAYOFMONTH().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[DAYNAME] +declaration=date +category=Date and Time Functions +description=Returns the name of the weekday for date. The language used for the\nname is controlled by the value of the lc_time_names system variable\n(see https://dev.mysql.com/doc/refman/8.3/en/locale-support.html).\nReturns NULL if date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[DAYOFMONTH] +declaration=date +category=Date and Time Functions +description=Returns the day of the month for date, in the range 1 to 31, or 0 for\ndates such as '0000-00-00' or '2008-00-00' that have a zero day part.\nReturns NULL if date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[DAYOFWEEK] +declaration=date +category=Date and Time Functions +description=Returns the weekday index for date (1 = Sunday, 2 = Monday, ..., 7 =\nSaturday). These index values correspond to the ODBC standard. Returns\nNULL if date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[DAYOFYEAR] +declaration=date +category=Date and Time Functions +description=Returns the day of the year for date, in the range 1 to 366. Returns\nNULL if date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[DEC] +declaration=M[,D] +category=Data Types +description=[ZEROFILL], FIXED[(M[,D])] [UNSIGNED] [ZEROFILL]\n\nThese types are synonyms for DECIMAL. The FIXED synonym is available\nfor compatibility with other database systems.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[DECIMAL] +declaration=M[,D] +category=Data Types +description=A packed "exact" fixed-point number. M is the total number of digits\n(the precision) and D is the number of digits after the decimal point\n(the scale). The decimal point and (for negative numbers) the - sign\nare not counted in M. If D is 0, values have no decimal point or\nfractional part. The maximum number of digits (M) for DECIMAL is 65.\nThe maximum number of supported decimals (D) is 30. If D is omitted,\nthe default is 0. If M is omitted, the default is 10. (There is also a\nlimit on how long the text of DECIMAL literals can be; see\nhttps://dev.mysql.com/doc/refman/8.3/en/precision-math-expressions.html\n.)\n\nUNSIGNED, if specified, disallows negative values. The UNSIGNED\nattribute is deprecated for columns of type DECIMAL (and any synonyms);\nyou should expect support for it to be removed in a future version of\nMySQL. Consider using a simple CHECK constraint instead for such\ncolumns.\n\nAll basic calculations (+, -, *, /) with DECIMAL columns are done with\na precision of 65 digits.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[DEFAULT] +declaration=col_name +category=Miscellaneous Functions +description=Returns the default value for a table column. An error results if the\ncolumn has no default value.\n\nThe use of DEFAULT(col_name) to specify the default value for a named\ncolumn is permitted only for columns that have a literal default value,\nnot for columns that have an expression default value.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[DEGREES] +declaration=X +category=Numeric Functions +description=Returns the argument X, converted from radians to degrees. Returns NULL\nif X is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[DENSE_RANK] +declaration= +category=Window Functions +description=Returns the rank of the current row within its partition, without gaps.\nPeers are considered ties and receive the same rank. This function\nassigns consecutive ranks to peer groups; the result is that groups of\nsize greater than one do not produce noncontiguous rank numbers. For an\nexample, see the RANK() function description.\n\nThis function should be used with ORDER BY to sort partition rows into\nthe desired order. Without ORDER BY, all rows are peers.\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[DOUBLE] +declaration=M,D +category=Data Types +description=A normal-size (double-precision) floating-point number. Permissible\nvalues are -1.7976931348623157E+308 to -2.2250738585072014E-308, 0, and\n2.2250738585072014E-308 to 1.7976931348623157E+308. These are the\ntheoretical limits, based on the IEEE standard. The actual range might\nbe slightly smaller depending on your hardware or operating system.\n\nM is the total number of digits and D is the number of digits following\nthe decimal point. If M and D are omitted, values are stored to the\nlimits permitted by the hardware. A double-precision floating-point\nnumber is accurate to approximately 15 decimal places.\n\nDOUBLE(M,D) is a nonstandard MySQL extension; and is deprecated. You\nshould expect support for this syntax to be removed in a future version\nof MySQL.\n\nUNSIGNED, if specified, disallows negative values. The UNSIGNED\nattribute is deprecated for columns of type DOUBLE (and any synonyms)\nand you should expect support for it to be removed in a future version\nof MySQL. Consider using a simple CHECK constraint instead for such\ncolumns.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[ELT] +declaration=N,str1,str2,str3,... +category=String Functions +description=ELT() returns the Nth element of the list of strings: str1 if N = 1,\nstr2 if N = 2, and so on. Returns NULL if N is less than 1, greater\nthan the number of arguments, or NULL. ELT() is the complement of\nFIELD().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[ENUM] +declaration='value1','value2',... +category=Data Types +description=collation_name]\n\nAn enumeration. A string object that can have only one value, chosen\nfrom the list of values 'value1', 'value2', ..., NULL or the special ''\nerror value. ENUM values are represented internally as integers.\n\nAn ENUM column can have a maximum of 65,535 distinct elements.\n\nThe maximum supported length of an individual ENUM element is M <= 255\nand (M x w) <= 1020, where M is the element literal length and w is the\nnumber of bytes required for the maximum-length character in the\ncharacter set.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html +[EXP] +declaration=X +category=Numeric Functions +description=Returns the value of e (the base of natural logarithms) raised to the\npower of X. The inverse of this function is LOG() (using a single\nargument only) or LN().\n\nIf X is NULL, this function returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[EXPORT_SET] +declaration=bits,on,off[,separator[,number_of_bits]] +category=String Functions +description=Returns a string such that for every bit set in the value bits, you get\nan on string and for every bit not set in the value, you get an off\nstring. Bits in bits are examined from right to left (from low-order to\nhigh-order bits). Strings are added to the result from left to right,\nseparated by the separator string (the default being the comma\ncharacter ,). The number of bits examined is given by number_of_bits,\nwhich has a default of 64 if not specified. number_of_bits is silently\nclipped to 64 if larger than 64. It is treated as an unsigned integer,\nso a value of −1 is effectively the same as 64.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[EXTRACT] +declaration=unit FROM date +category=Date and Time Functions +description=The EXTRACT() function uses the same kinds of unit specifiers as\nDATE_ADD() or DATE_SUB(), but extracts parts from the date rather than\nperforming date arithmetic. For information on the unit argument, see\nhttps://dev.mysql.com/doc/refman/8.3/en/expressions.html#temporal-inter\nvals. Returns NULL if date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[EXTRACTVALUE] +declaration=xml_frag, xpath_expr +category=XML +description=ExtractValue() takes two string arguments, a fragment of XML markup\nxml_frag and an XPath expression xpath_expr (also known as a locator);\nit returns the text (CDATA) of the first text node which is a child of\nthe element or elements matched by the XPath expression.\n\nUsing this function is the equivalent of performing a match using the\nxpath_expr after appending /text(). In other words,\nExtractValue('Sakila', '/a/b') and\nExtractValue('Sakila', '/a/b/text()') produce the same\nresult. If xml_frag or xpath_expr is NULL, the function returns NULL.\n\nIf multiple matches are found, the content of the first child text node\nof each matching element is returned (in the order matched) as a\nsingle, space-delimited string.\n\nIf no matching text node is found for the expression (including the\nimplicit /text())---for whatever reason, as long as xpath_expr is\nvalid, and xml_frag consists of elements which are properly nested and\nclosed---an empty string is returned. No distinction is made between a\nmatch on an empty element and no match at all. This is by design.\n\nIf you need to determine whether no matching element was found in\nxml_frag or such an element was found but contained no child text\nnodes, you should test the result of an expression that uses the XPath\ncount() function. For example, both of these statements return an empty\nstring, as shown here:\n\nmysql> SELECT ExtractValue('', '/a/b');\n+-------------------------------------+\n| ExtractValue('', '/a/b') |\n+-------------------------------------+\n| |\n+-------------------------------------+\n1 row in set (0.00 sec)\n\nmysql> SELECT ExtractValue('', '/a/b');\n+-------------------------------------+\n| ExtractValue('', '/a/b') |\n+-------------------------------------+\n| |\n+-------------------------------------+\n1 row in set (0.00 sec)\n\nHowever, you can determine whether there was actually a matching\nelement using the following:\n\nmysql> SELECT ExtractValue('', 'count(/a/b)');\n+-------------------------------------+\n| ExtractValue('', 'count(/a/b)') |\n+-------------------------------------+\n ... +[FIELD] +declaration=str,str1,str2,str3,... +category=String Functions +description=Returns the index (position) of str in the str1, str2, str3, ... list.\nReturns 0 if str is not found.\n\nIf all arguments to FIELD() are strings, all arguments are compared as\nstrings. If all arguments are numbers, they are compared as numbers.\nOtherwise, the arguments are compared as double.\n\nIf str is NULL, the return value is 0 because NULL fails equality\ncomparison with any value. FIELD() is the complement of ELT().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[FIND_IN_SET] +declaration=str,strlist +category=String Functions +description=Returns a value in the range of 1 to N if the string str is in the\nstring list strlist consisting of N substrings. A string list is a\nstring composed of substrings separated by , characters. If the first\nargument is a constant string and the second is a column of type SET,\nthe FIND_IN_SET() function is optimized to use bit arithmetic. Returns\n0 if str is not in strlist or if strlist is the empty string. Returns\nNULL if either argument is NULL. This function does not work properly\nif the first argument contains a comma (,) character.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[FIRST_VALUE] +declaration=expr +category=Window Functions +description=Returns the value of expr from the first row of the window frame.\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\nnull_treatment is as described in the section introduction.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[FLOAT] +declaration=M,D +category=Data Types +description=A small (single-precision) floating-point number. Permissible values\nare -3.402823466E+38 to -1.175494351E-38, 0, and 1.175494351E-38 to\n3.402823466E+38. These are the theoretical limits, based on the IEEE\nstandard. The actual range might be slightly smaller depending on your\nhardware or operating system.\n\nM is the total number of digits and D is the number of digits following\nthe decimal point. If M and D are omitted, values are stored to the\nlimits permitted by the hardware. A single-precision floating-point\nnumber is accurate to approximately 7 decimal places.\n\nFLOAT(M,D) is a nonstandard MySQL extension. This syntax is deprecated,\nand you should expect support for it to be removed in a future version\nof MySQL.\n\nUNSIGNED, if specified, disallows negative values. The UNSIGNED\nattribute is deprecated for columns of type FLOAT (and any synonyms)\nand you should expect support for it to be removed in a future version\nof MySQL. Consider using a simple CHECK constraint instead for such\ncolumns.\n\nUsing FLOAT might give you some unexpected problems because all\ncalculations in MySQL are done with double precision. See\nhttps://dev.mysql.com/doc/refman/8.3/en/no-matching-rows.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[FLOOR] +declaration=X +category=Numeric Functions +description=Returns the largest integer value not greater than X. Returns NULL if X\nis NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[FORMAT] +declaration=X,D[,locale] +category=String Functions +description=Formats the number X to a format like '#,###,###.##', rounded to D\ndecimal places, and returns the result as a string. If D is 0, the\nresult has no decimal point or fractional part. If X or D is NULL, the\nfunction returns NULL.\n\nThe optional third parameter enables a locale to be specified to be\nused for the result number's decimal point, thousands separator, and\ngrouping between separators. Permissible locale values are the same as\nthe legal values for the lc_time_names system variable (see\nhttps://dev.mysql.com/doc/refman/8.3/en/locale-support.html). If the\nlocale is NULL or not specified, the default locale is 'en_US'.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[FORMAT_BYTES] +declaration=count +category=Performance Schema Functions +description=Given a numeric byte count, converts it to human-readable format and\nreturns a string consisting of a value and a units indicator. The\nstring contains the number of bytes rounded to 2 decimal places and a\nminimum of 3 significant digits. Numbers less than 1024 bytes are\nrepresented as whole numbers and are not rounded. Returns NULL if count\nis NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/performance-schema-functions.html +[FORMAT_PICO_TIME] +declaration=time_val +category=Performance Schema Functions +description=Given a numeric Performance Schema latency or wait time in picoseconds,\nconverts it to human-readable format and returns a string consisting of\na value and a units indicator. The string contains the decimal time\nrounded to 2 decimal places and a minimum of 3 significant digits.\nTimes under 1 nanosecond are represented as whole numbers and are not\nrounded.\n\nIf time_val is NULL, this function returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/performance-schema-functions.html +[FOUND_ROWS] +declaration= +category=Information Functions +description=*Note*:\n\nThe SQL_CALC_FOUND_ROWS query modifier and accompanying FOUND_ROWS()\nfunction are deprecated; expect them to be removed in a future version\nof MySQL. Execute the query with LIMIT, and then a second query with\nCOUNT(*) and without LIMIT to determine whether there are additional\nrows. For example, instead of these queries:\n\nSELECT SQL_CALC_FOUND_ROWS * FROM tbl_name WHERE id > 100 LIMIT 10;\nSELECT FOUND_ROWS();\n\nUse these queries instead:\n\nSELECT * FROM tbl_name WHERE id > 100 LIMIT 10;\nSELECT COUNT(*) FROM tbl_name WHERE id > 100;\n\nCOUNT(*) is subject to certain optimizations. SQL_CALC_FOUND_ROWS\ncauses some optimizations to be disabled.\n\nA SELECT statement may include a LIMIT clause to restrict the number of\nrows the server returns to the client. In some cases, it is desirable\nto know how many rows the statement would have returned without the\nLIMIT, but without running the statement again. To obtain this row\ncount, include an SQL_CALC_FOUND_ROWS option in the SELECT statement,\nand then invoke FOUND_ROWS() afterward:\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[FROM_BASE64] +declaration=str +category=String Functions +description=Takes a string encoded with the base-64 encoded rules used by\nTO_BASE64() and returns the decoded result as a binary string. The\nresult is NULL if the argument is NULL or not a valid base-64 string.\nSee the description of TO_BASE64() for details about the encoding and\ndecoding rules.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[FROM_DAYS] +declaration=N +category=Date and Time Functions +description=Given a day number N, returns a DATE value. Returns NULL if N is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[FROM_UNIXTIME] +declaration=unix_timestamp[,format] +category=Date and Time Functions +description=Returns a representation of unix_timestamp as a datetime or character\nstring value. The value returned is expressed using the session time\nzone. (Clients can set the session time zone as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/time-zone-support.html.)\nunix_timestamp is an internal timestamp value representing seconds\nsince '1970-01-01 00:00:00' UTC, such as produced by the\nUNIX_TIMESTAMP() function.\n\nIf format is omitted, this function returns a DATETIME value.\n\nIf unix_timestamp or format is NULL, this function returns NULL.\n\nIf unix_timestamp is an integer, the fractional seconds precision of\nthe DATETIME is zero. When unix_timestamp is a decimal value, the\nfractional seconds precision of the DATETIME is the same as the\nprecision of the decimal value, up to a maximum of 6. When\nunix_timestamp is a floating point number, the fractional seconds\nprecision of the datetime is 6.\n\nOn 32-bit platforms, the maximum useful value for unix_timestamp is\n2147483647.999999, which returns '2038-01-19 03:14:07.999999' UTC. On\n64-bit platforms, the effective maximum is 32536771199.999999, which\nreturns '3001-01-18 23:59:59.999999' UTC. Regardless of platform or\nversion, a greater value for unix_timestamp than the effective maximum\nreturns 0.\n\nformat is used to format the result in the same way as the format\nstring used for the DATE_FORMAT() function. If format is supplied, the\nvalue returned is a VARCHAR.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[GEOMCOLLECTION] +declaration=g [, g] ... +category=Geometry Constructors +description=Constructs a GeomCollection value from the geometry arguments.\n\nGeomCollection() returns all the proper geometries contained in the\narguments even if a nonsupported geometry is present.\n\nGeomCollection() with no arguments is permitted as a way to create an\nempty geometry. Also, functions such as ST_GeomFromText() that accept\nWKT geometry collection arguments understand both OpenGIS\n'GEOMETRYCOLLECTION EMPTY' standard syntax and MySQL\n'GEOMETRYCOLLECTION()' nonstandard syntax.\n\nGeomCollection() and GeometryCollection() are synonymous, with\nGeomCollection() the preferred function.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html +[GEOMETRYCOLLECTION] +declaration=g [, g] ... +category=Geometry Constructors +description=Constructs a GeomCollection value from the geometry arguments.\n\nGeometryCollection() returns all the proper geometries contained in the\narguments even if a nonsupported geometry is present.\n\nGeometryCollection() with no arguments is permitted as a way to create\nan empty geometry. Also, functions such as ST_GeomFromText() that\naccept WKT geometry collection arguments understand both OpenGIS\n'GEOMETRYCOLLECTION EMPTY' standard syntax and MySQL\n'GEOMETRYCOLLECTION()' nonstandard syntax.\n\nGeomCollection() and GeometryCollection() are synonymous, with\nGeomCollection() the preferred function.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html +[GET_FORMAT] +declaration={DATE|TIME|DATETIME}, {'EUR'|'USA'|'JIS'|'ISO'|'INTERNAL'} +category=Date and Time Functions +description=Returns a format string. This function is useful in combination with\nthe DATE_FORMAT() and the STR_TO_DATE() functions.\n\nIf format is NULL, this function returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[GET_LOCK] +declaration=str,timeout +category=Locking Functions +description=Tries to obtain a lock with a name given by the string str, using a\ntimeout of timeout seconds. A negative timeout value means infinite\ntimeout. The lock is exclusive. While held by one session, other\nsessions cannot obtain a lock of the same name.\n\nReturns 1 if the lock was obtained successfully, 0 if the attempt timed\nout (for example, because another client has previously locked the\nname), or NULL if an error occurred (such as running out of memory or\nthe thread was killed with mysqladmin kill).\n\nA lock obtained with GET_LOCK() is released explicitly by executing\nRELEASE_LOCK() or implicitly when your session terminates (either\nnormally or abnormally). Locks obtained with GET_LOCK() are not\nreleased when transactions commit or roll back.\n\nGET_LOCK() is implemented using the metadata locking (MDL) subsystem.\nMultiple simultaneous locks can be acquired and GET_LOCK() does not\nrelease any existing locks. For example, suppose that you execute these\nstatements:\n\nSELECT GET_LOCK('lock1',10);\nSELECT GET_LOCK('lock2',10);\nSELECT RELEASE_LOCK('lock2');\nSELECT RELEASE_LOCK('lock1');\n\nThe second GET_LOCK() acquires a second lock and both RELEASE_LOCK()\ncalls return 1 (success).\n\nIt is even possible for a given session to acquire multiple locks for\nthe same name. Other sessions cannot acquire a lock with that name\nuntil the acquiring session releases all its locks for the name.\n\nUniquely named locks acquired with GET_LOCK() appear in the Performance\nSchema metadata_locks table. The OBJECT_TYPE column says USER LEVEL\nLOCK and the OBJECT_NAME column indicates the lock name. In the case\nthat multiple locks are acquired for the same name, only the first lock\nfor the name registers a row in the metadata_locks table. Subsequent\nlocks for the name increment a counter in the lock but do not acquire\nadditional metadata locks. The metadata_locks row for the lock is\ndeleted when the last lock instance on the name is released.\n\nThe capability of acquiring multiple locks means there is the\npossibility of deadlock among clients. When this happens, the server\nchooses a caller and terminates its lock-acquisition request with an\nER_USER_LOCK_DEADLOCK\n(https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html\n#error_er_user_lock_deadlock) error. This error does not cause\ntransactions to roll back.\n\nMySQL enforces a maximum length on lock names of 64 characters.\n ... +[GREATEST] +declaration=value1,value2,... +category=Comparison Operators +description=With two or more arguments, returns the largest (maximum-valued)\nargument. The arguments are compared using the same rules as for\nLEAST().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html +[GROUPING] +declaration=expr [, expr] ... +category=Miscellaneous Functions +description=For GROUP BY queries that include a WITH ROLLUP modifier, the ROLLUP\noperation produces super-aggregate output rows where NULL represents\nthe set of all values. The GROUPING() function enables you to\ndistinguish NULL values for super-aggregate rows from NULL values in\nregular grouped rows.\n\nGROUPING() is permitted in the select list, HAVING clause, and ORDER BY\nclause.\n\nEach argument to GROUPING() must be an expression that exactly matches\nan expression in the GROUP BY clause. The expression cannot be a\npositional specifier. For each expression, GROUPING() produces 1 if the\nexpression value in the current row is a NULL representing a\nsuper-aggregate value. Otherwise, GROUPING() produces 0, indicating\nthat the expression value is a NULL for a regular result row or is not\nNULL.\n\nSuppose that table t1 contains these rows, where NULL indicates\nsomething like "other" or "unknown":\n\nmysql> SELECT * FROM t1;\n+------+-------+----------+\n| name | size | quantity |\n+------+-------+----------+\n| ball | small | 10 |\n| ball | large | 20 |\n| ball | NULL | 5 |\n| hoop | small | 15 |\n| hoop | large | 5 |\n| hoop | NULL | 3 |\n+------+-------+----------+\n\nA summary of the table without WITH ROLLUP looks like this:\n\nmysql> SELECT name, size, SUM(quantity) AS quantity\n FROM t1\n GROUP BY name, size;\n+------+-------+----------+\n| name | size | quantity |\n+------+-------+----------+\n| ball | small | 10 |\n| ball | large | 20 |\n| ball | NULL | 5 |\n| hoop | small | 15 |\n| hoop | large | 5 |\n| hoop | NULL | 3 |\n+------+-------+----------+\n\nThe result contains NULL values, but those do not represent\nsuper-aggregate rows because the query does not include WITH ROLLUP.\n ... +[GROUP_CONCAT] +declaration=expr +category=Aggregate Functions and Modifiers +description=This function returns a string result with the concatenated non-NULL\nvalues from a group. It returns NULL if there are no non-NULL values.\nThe full syntax is as follows:\n\nGROUP_CONCAT([DISTINCT] expr [,expr ...]\n [ORDER BY {unsigned_integer | col_name | expr}\n [ASC | DESC] [,col_name ...]]\n [SEPARATOR str_val])\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[GTID_SUBSET] +declaration=set1,set2 +category=GTID +description=Given two sets of global transaction identifiers set1 and set2, returns\ntrue if all GTIDs in set1 are also in set2. Returns NULL if set1 or\nset2 is NULL. Returns false otherwise.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gtid-functions.html +[GTID_SUBTRACT] +declaration=set1,set2 +category=GTID +description=Given two sets of global transaction identifiers set1 and set2, returns\nonly those GTIDs from set1 that are not in set2. Returns NULL if set1\nor set2 is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gtid-functions.html +[HEX] +declaration=str +category=String Functions +description=For a string argument str, HEX() returns a hexadecimal string\nrepresentation of str where each byte of each character in str is\nconverted to two hexadecimal digits. (Multibyte characters therefore\nbecome more than two digits.) The inverse of this operation is\nperformed by the UNHEX() function.\n\nFor a numeric argument N, HEX() returns a hexadecimal string\nrepresentation of the value of N treated as a longlong (BIGINT) number.\nThis is equivalent to CONV(N,10,16). The inverse of this operation is\nperformed by CONV(HEX(N),16,10).\n\nFor a NULL argument, this function returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[HOUR] +declaration=time +category=Date and Time Functions +description=Returns the hour for time. The range of the return value is 0 to 23 for\ntime-of-day values. However, the range of TIME values actually is much\nlarger, so HOUR can return values greater than 23. Returns NULL if time\nis NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[ICU_VERSION] +declaration= +category=Information Functions +description=The version of the International Components for Unicode (ICU) library\nused to support regular expression operations (see\nhttps://dev.mysql.com/doc/refman/8.3/en/regexp.html). This function is\nprimarily intended for use in test cases.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[IFNULL] +declaration=expr1,expr2 +category=Flow Control Functions +description=If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns\nexpr2.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/flow-control-functions.html +[IN] +declaration=value,... +category=Comparison Operators +description=Returns 1 (true) if expr is equal to any of the values in the IN()\nlist, else returns 0 (false).\n\nType conversion takes place according to the rules described in\nhttps://dev.mysql.com/doc/refman/8.3/en/type-conversion.html, applied\nto all the arguments. If no type conversion is needed for the values in\nthe IN() list, they are all non-JSON constants of the same type, and\nexpr can be compared to each of them as a value of the same type\n(possibly after type conversion), an optimization takes place. The\nvalues the list are sorted and the search for expr is done using a\nbinary search, which makes the IN() operation very quick.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html +[INET6_ATON] +declaration=expr +category=Miscellaneous Functions +description=Given an IPv6 or IPv4 network address as a string, returns a binary\nstring that represents the numeric value of the address in network byte\norder (big endian). Because numeric-format IPv6 addresses require more\nbytes than the largest integer type, the representation returned by\nthis function has the VARBINARY data type: VARBINARY(16) for IPv6\naddresses and VARBINARY(4) for IPv4 addresses. If the argument is not a\nvalid address, or if it is NULL, INET6_ATON() returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[INET6_NTOA] +declaration=expr +category=Miscellaneous Functions +description=Given an IPv6 or IPv4 network address represented in numeric form as a\nbinary string, returns the string representation of the address as a\nstring in the connection character set. If the argument is not a valid\naddress, or if it is NULL, INET6_NTOA() returns NULL.\n\nINET6_NTOA() has these properties:\n\no It does not use operating system functions to perform conversions,\n thus the output string is platform independent.\n\no The return string has a maximum length of 39 (4 x 8 + 7). Given this\n statement:\n\nCREATE TABLE t AS SELECT INET6_NTOA(expr) AS c1;\n\n The resulting table would have this definition:\n\nCREATE TABLE t (c1 VARCHAR(39) CHARACTER SET utf8mb3 DEFAULT NULL);\n\no The return string uses lowercase letters for IPv6 addresses.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[INET_ATON] +declaration=expr +category=Miscellaneous Functions +description=Given the dotted-quad representation of an IPv4 network address as a\nstring, returns an integer that represents the numeric value of the\naddress in network byte order (big endian). INET_ATON() returns NULL if\nit does not understand its argument, or if expr is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[INET_NTOA] +declaration=expr +category=Miscellaneous Functions +description=Given a numeric IPv4 network address in network byte order, returns the\ndotted-quad string representation of the address as a string in the\nconnection character set. INET_NTOA() returns NULL if it does not\nunderstand its argument.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[INSTR] +declaration=str,substr +category=String Functions +description=Returns the position of the first occurrence of substring substr in\nstring str. This is the same as the two-argument form of LOCATE(),\nexcept that the order of the arguments is reversed.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[INT] +declaration=M +category=Data Types +description=A normal-size integer. The signed range is -2147483648 to 2147483647.\nThe unsigned range is 0 to 4294967295.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[INTEGER] +declaration=M +category=Data Types +description=This type is a synonym for INT.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[INTERVAL] +declaration=N,N1,N2,N3,... +category=Comparison Operators +description=Returns 0 if N <= N1, 1 if N <= N2 and so on, or -1 if N is NULL. All\narguments are treated as integers. It is required that N1 <= N2 <= N3\n<= ... <= Nn for this function to work correctly. This is because a\nbinary search is used (very fast).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html +[ISNULL] +declaration=expr +category=Comparison Operators +description=If expr is NULL, ISNULL() returns 1, otherwise it returns 0.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html +[IS_FREE_LOCK] +declaration=str +category=Locking Functions +description=Checks whether the lock named str is free to use (that is, not locked).\nReturns 1 if the lock is free (no one is using the lock), 0 if the lock\nis in use, and NULL if an error occurs (such as an incorrect argument).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/locking-functions.html +[IS_IPV4] +declaration=expr +category=Miscellaneous Functions +description=Returns 1 if the argument is a valid IPv4 address specified as a\nstring, 0 otherwise. Returns NULL if expr is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[IS_IPV4_COMPAT] +declaration=expr +category=Miscellaneous Functions +description=This function takes an IPv6 address represented in numeric form as a\nbinary string, as returned by INET6_ATON(). It returns 1 if the\nargument is a valid IPv4-compatible IPv6 address, 0 otherwise (unless\nexpr is NULL, in which case the function returns NULL). IPv4-compatible\naddresses have the form ::ipv4_address.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[IS_IPV4_MAPPED] +declaration=expr +category=Miscellaneous Functions +description=This function takes an IPv6 address represented in numeric form as a\nbinary string, as returned by INET6_ATON(). It returns 1 if the\nargument is a valid IPv4-mapped IPv6 address, 0 otherwise, unless expr\nis NULL, in which case the function returns NULL. IPv4-mapped addresses\nhave the form ::ffff:ipv4_address.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[IS_IPV6] +declaration=expr +category=Miscellaneous Functions +description=Returns 1 if the argument is a valid IPv6 address specified as a\nstring, 0 otherwise, unless expr is NULL, in which case the function\nreturns NULL. This function does not consider IPv4 addresses to be\nvalid IPv6 addresses.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[IS_USED_LOCK] +declaration=str +category=Locking Functions +description=Checks whether the lock named str is in use (that is, locked). If so,\nit returns the connection identifier of the client session that holds\nthe lock. Otherwise, it returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/locking-functions.html +[IS_UUID] +declaration=string_uuid +category=Miscellaneous Functions +description=Returns 1 if the argument is a valid string-format UUID, 0 if the\nargument is not a valid UUID, and NULL if the argument is NULL.\n\n"Valid" means that the value is in a format that can be parsed. That\nis, it has the correct length and contains only the permitted\ncharacters (hexadecimal digits in any lettercase and, optionally,\ndashes and curly braces). This format is most common:\n\naaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\n\nThese other formats are also permitted:\n\naaaaaaaabbbbccccddddeeeeeeeeeeee\n{aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee}\n\nFor the meanings of fields within the value, see the UUID() function\ndescription.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[JOIN] +declaration=t2, t3, t4 +category=Data Manipulation +description=ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\n\nis equivalent to:\n\nSELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)\n ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\n\nIn MySQL, JOIN, CROSS JOIN, and INNER JOIN are syntactic equivalents\n(they can replace each other). In standard SQL, they are not\nequivalent. INNER JOIN is used with an ON clause, CROSS JOIN is used\notherwise.\n\nIn general, parentheses can be ignored in join expressions containing\nonly inner join operations. MySQL also supports nested joins. See\nhttps://dev.mysql.com/doc/refman/8.3/en/nested-join-optimization.html.\n\nIndex hints can be specified to affect how the MySQL optimizer makes\nuse of indexes. For more information, see\nhttps://dev.mysql.com/doc/refman/8.3/en/index-hints.html. Optimizer\nhints and the optimizer_switch system variable are other ways to\ninfluence optimizer use of indexes. See\nhttps://dev.mysql.com/doc/refman/8.3/en/optimizer-hints.html, and\nhttps://dev.mysql.com/doc/refman/8.3/en/switchable-optimizations.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/join.html +[JSON_ARRAY] +declaration=[val[, val] ...] +category=MBR Functions +description=Evaluates a (possibly empty) list of values and returns a JSON array\ncontaining those values.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-creation-functions.html +[JSON_ARRAYAGG] +declaration=col_or_expr +category=Aggregate Functions and Modifiers +description=Aggregates a result set as a single JSON array whose elements consist\nof the rows. The order of elements in this array is undefined. The\nfunction acts on a column or an expression that evaluates to a single\nvalue. Returns NULL if the result contains no rows, or in the event of\nan error. If col_or_expr is NULL, the function returns an array of JSON\n[null] elements.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[JSON_ARRAY_APPEND] +declaration=json_doc, path, val[, path, val] ... +category=MBR Functions +description=Appends values to the end of the indicated arrays within a JSON\ndocument and returns the result. Returns NULL if any argument is NULL.\nAn error occurs if the json_doc argument is not a valid JSON document\nor any path argument is not a valid path expression or contains a * or\n** wildcard.\n\nThe path-value pairs are evaluated left to right. The document produced\nby evaluating one pair becomes the new value against which the next\npair is evaluated.\n\nIf a path selects a scalar or object value, that value is autowrapped\nwithin an array and the new value is added to that array. Pairs for\nwhich the path does not identify any value in the JSON document are\nignored.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html +[JSON_ARRAY_INSERT] +declaration=json_doc, path, val[, path, val] ... +category=MBR Functions +description=Updates a JSON document, inserting into an array within the document\nand returning the modified document. Returns NULL if any argument is\nNULL. An error occurs if the json_doc argument is not a valid JSON\ndocument or any path argument is not a valid path expression or\ncontains a * or ** wildcard or does not end with an array element\nidentifier.\n\nThe path-value pairs are evaluated left to right. The document produced\nby evaluating one pair becomes the new value against which the next\npair is evaluated.\n\nPairs for which the path does not identify any array in the JSON\ndocument are ignored. If a path identifies an array element, the\ncorresponding value is inserted at that element position, shifting any\nfollowing values to the right. If a path identifies an array position\npast the end of an array, the value is inserted at the end of the\narray.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html +[JSON_CONTAINS] +declaration=target, candidate[, path] +category=MBR Functions +description=Indicates by returning 1 or 0 whether a given candidate JSON document\nis contained within a target JSON document, or---if a path argument was\nsupplied---whether the candidate is found at a specific path within the\ntarget. Returns NULL if any argument is NULL, or if the path argument\ndoes not identify a section of the target document. An error occurs if\ntarget or candidate is not a valid JSON document, or if the path\nargument is not a valid path expression or contains a * or ** wildcard.\n\nTo check only whether any data exists at the path, use\nJSON_CONTAINS_PATH() instead.\n\nThe following rules define containment:\n\no A candidate scalar is contained in a target scalar if and only if\n they are comparable and are equal. Two scalar values are comparable\n if they have the same JSON_TYPE() types, with the exception that\n values of types INTEGER and DECIMAL are also comparable to each\n other.\n\no A candidate array is contained in a target array if and only if every\n element in the candidate is contained in some element of the target.\n\no A candidate nonarray is contained in a target array if and only if\n the candidate is contained in some element of the target.\n\no A candidate object is contained in a target object if and only if for\n each key in the candidate there is a key with the same name in the\n target and the value associated with the candidate key is contained\n in the value associated with the target key.\n\nOtherwise, the candidate value is not contained in the target document.\n\nQueries using JSON_CONTAINS() on InnoDB tables can be optimized using\nmulti-valued indexes; see\nhttps://dev.mysql.com/doc/refman/8.3/en/create-index.html#create-index-\nmulti-valued, for more information.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html +[JSON_CONTAINS_PATH] +declaration=json_doc, one_or_all, path[, path] ... +category=MBR Functions +description=Returns 0 or 1 to indicate whether a JSON document contains data at a\ngiven path or paths. Returns NULL if any argument is NULL. An error\noccurs if the json_doc argument is not a valid JSON document, any path\nargument is not a valid path expression, or one_or_all is not 'one' or\n'all'.\n\nTo check for a specific value at a path, use JSON_CONTAINS() instead.\n\nThe return value is 0 if no specified path exists within the document.\nOtherwise, the return value depends on the one_or_all argument:\n\no 'one': 1 if at least one path exists within the document, 0\n otherwise.\n\no 'all': 1 if all paths exist within the document, 0 otherwise.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html +[JSON_DEPTH] +declaration=json_doc +category=MBR Functions +description=Returns the maximum depth of a JSON document. Returns NULL if the\nargument is NULL. An error occurs if the argument is not a valid JSON\ndocument.\n\nAn empty array, empty object, or scalar value has depth 1. A nonempty\narray containing only elements of depth 1 or nonempty object containing\nonly member values of depth 1 has depth 2. Otherwise, a JSON document\nhas depth greater than 2.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-attribute-functions.html +[JSON_EXTRACT] +declaration=json_doc, path[, path] ... +category=MBR Functions +description=Returns data from a JSON document, selected from the parts of the\ndocument matched by the path arguments. Returns NULL if any argument is\nNULL or no paths locate a value in the document. An error occurs if the\njson_doc argument is not a valid JSON document or any path argument is\nnot a valid path expression.\n\nThe return value consists of all values matched by the path arguments.\nIf it is possible that those arguments could return multiple values,\nthe matched values are autowrapped as an array, in the order\ncorresponding to the paths that produced them. Otherwise, the return\nvalue is the single matched value.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html +[JSON_INSERT] +declaration=json_doc, path, val[, path, val] ... +category=MBR Functions +description=Inserts data into a JSON document and returns the result. Returns NULL\nif any argument is NULL. An error occurs if the json_doc argument is\nnot a valid JSON document or any path argument is not a valid path\nexpression or contains a * or ** wildcard.\n\nThe path-value pairs are evaluated left to right. The document produced\nby evaluating one pair becomes the new value against which the next\npair is evaluated.\n\nA path-value pair for an existing path in the document is ignored and\ndoes not overwrite the existing document value. A path-value pair for a\nnonexisting path in the document adds the value to the document if the\npath identifies one of these types of values:\n\no A member not present in an existing object. The member is added to\n the object and associated with the new value.\n\no A position past the end of an existing array. The array is extended\n with the new value. If the existing value is not an array, it is\n autowrapped as an array, then extended with the new value.\n\nOtherwise, a path-value pair for a nonexisting path in the document is\nignored and has no effect.\n\nFor a comparison of JSON_INSERT(), JSON_REPLACE(), and JSON_SET(), see\nthe discussion of JSON_SET().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html +[JSON_KEYS] +declaration=json_doc[, path] +category=MBR Functions +description=Returns the keys from the top-level value of a JSON object as a JSON\narray, or, if a path argument is given, the top-level keys from the\nselected path. Returns NULL if any argument is NULL, the json_doc\nargument is not an object, or path, if given, does not locate an\nobject. An error occurs if the json_doc argument is not a valid JSON\ndocument or the path argument is not a valid path expression or\ncontains a * or ** wildcard.\n\nThe result array is empty if the selected object is empty. If the\ntop-level value has nested subobjects, the return value does not\ninclude keys from those subobjects.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html +[JSON_LENGTH] +declaration=json_doc[, path] +category=MBR Functions +description=Returns the length of a JSON document, or, if a path argument is given,\nthe length of the value within the document identified by the path.\nReturns NULL if any argument is NULL or the path argument does not\nidentify a value in the document. An error occurs if the json_doc\nargument is not a valid JSON document or the path argument is not a\nvalid path expression.\n\nThe length of a document is determined as follows:\n\no The length of a scalar is 1.\n\no The length of an array is the number of array elements.\n\no The length of an object is the number of object members.\n\no The length does not count the length of nested arrays or objects.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-attribute-functions.html +[JSON_MERGE] +declaration=json_doc, json_doc[, json_doc] ... +category=MBR Functions +description=Deprecated synonym for JSON_MERGE_PRESERVE().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html +[JSON_OBJECT] +declaration=[key, val[, key, val] ...] +category=MBR Functions +description=Evaluates a (possibly empty) list of key-value pairs and returns a JSON\nobject containing those pairs. An error occurs if any key name is NULL\nor the number of arguments is odd.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-creation-functions.html +[JSON_OBJECTAGG] +declaration=key, value +category=Aggregate Functions and Modifiers +description=Takes two column names or expressions as arguments, the first of these\nbeing used as a key and the second as a value, and returns a JSON\nobject containing key-value pairs. Returns NULL if the result contains\nno rows, or in the event of an error. An error occurs if any key name\nis NULL or the number of arguments is not equal to 2.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[JSON_OVERLAPS] +declaration=json_doc1, json_doc2 +category=MBR Functions +description=Compares two JSON documents. Returns true (1) if the two document have\nany key-value pairs or array elements in common. If both arguments are\nscalars, the function performs a simple equality test. If either\nargument is NULL, the function returns NULL.\n\nThis function serves as counterpart to JSON_CONTAINS(), which requires\nall elements of the array searched for to be present in the array\nsearched in. Thus, JSON_CONTAINS() performs an AND operation on search\nkeys, while JSON_OVERLAPS() performs an OR operation.\n\nQueries on JSON columns of InnoDB tables using JSON_OVERLAPS() in the\nWHERE clause can be optimized using multi-valued indexes.\nhttps://dev.mysql.com/doc/refman/8.3/en/create-index.html#create-index-\nmulti-valued, provides detailed information and examples.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html +[JSON_PRETTY] +declaration=json_val +category=MBR Functions +description=Provides pretty-printing of JSON values similar to that implemented in\nPHP and by other languages and database systems. The value supplied\nmust be a JSON value or a valid string representation of a JSON value.\nExtraneous whitespaces and newlines present in this value have no\neffect on the output. For a NULL value, the function returns NULL. If\nthe value is not a JSON document, or if it cannot be parsed as one, the\nfunction fails with an error.\n\nFormatting of the output from this function adheres to the following\nrules:\n\no Each array element or object member appears on a separate line,\n indented by one additional level as compared to its parent.\n\no Each level of indentation adds two leading spaces.\n\no A comma separating individual array elements or object members is\n printed before the newline that separates the two elements or\n members.\n\no The key and the value of an object member are separated by a colon\n followed by a space (': ').\n\no An empty object or array is printed on a single line. No space is\n printed between the opening and closing brace.\n\no Special characters in string scalars and key names are escaped\n employing the same rules used by the JSON_QUOTE() function.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-utility-functions.html +[JSON_QUOTE] +declaration=string +category=MBR Functions +description=Quotes a string as a JSON value by wrapping it with double quote\ncharacters and escaping interior quote and other characters, then\nreturning the result as a utf8mb4 string. Returns NULL if the argument\nis NULL.\n\nThis function is typically used to produce a valid JSON string literal\nfor inclusion within a JSON document.\n\nCertain special characters are escaped with backslashes per the escape\nsequences shown in\nhttps://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html\n#json-unquote-character-escape-sequences.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-creation-functions.html +[JSON_REMOVE] +declaration=json_doc, path[, path] ... +category=MBR Functions +description=Removes data from a JSON document and returns the result. Returns NULL\nif any argument is NULL. An error occurs if the json_doc argument is\nnot a valid JSON document or any path argument is not a valid path\nexpression or is $ or contains a * or ** wildcard.\n\nThe path arguments are evaluated left to right. The document produced\nby evaluating one path becomes the new value against which the next\npath is evaluated.\n\nIt is not an error if the element to be removed does not exist in the\ndocument; in that case, the path does not affect the document.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html +[JSON_REPLACE] +declaration=json_doc, path, val[, path, val] ... +category=MBR Functions +description=Replaces existing values in a JSON document and returns the result.\nReturns NULL if any argument is NULL. An error occurs if the json_doc\nargument is not a valid JSON document or any path argument is not a\nvalid path expression or contains a * or ** wildcard.\n\nThe path-value pairs are evaluated left to right. The document produced\nby evaluating one pair becomes the new value against which the next\npair is evaluated.\n\nA path-value pair for an existing path in the document overwrites the\nexisting document value with the new value. A path-value pair for a\nnonexisting path in the document is ignored and has no effect.\n\nThe optimizer can perform a partial, in-place update of a JSON column\ninstead of removing the old document and writing the new document in\nits entirety to the column. This optimization can be performed for an\nupdate statement that uses the JSON_REPLACE() function and meets the\nconditions outlined in\nhttps://dev.mysql.com/doc/refman/8.3/en/json.html#json-partial-updates.\n\nFor a comparison of JSON_INSERT(), JSON_REPLACE(), and JSON_SET(), see\nthe discussion of JSON_SET().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html +[JSON_SCHEMA_VALID] +declaration=schema,document +category=MBR Functions +description=Validates a JSON document against a JSON schema. Both schema and\ndocument are required. The schema must be a valid JSON object; the\ndocument must be a valid JSON document. Provided that these conditions\nare met: If the document validates against the schema, the function\nreturns true (1); otherwise, it returns false (0).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-validation-functions.html +[JSON_SCHEMA_VALIDATION_REPORT] +declaration=schema,document +category=MBR Functions +description=Validates a JSON document against a JSON schema. Both schema and\ndocument are required. As with JSON_VALID_SCHEMA(), the schema must be\na valid JSON object, and the document must be a valid JSON document.\nProvided that these conditions are met, the function returns a report,\nas a JSON document, on the outcome of the validation. If the JSON\ndocument is considered valid according to the JSON Schema, the function\nreturns a JSON object with one property valid having the value "true".\nIf the JSON document fails validation, the function returns a JSON\nobject which includes the properties listed here:\n\no valid: Always "false" for a failed schema validation\n\no reason: A human-readable string containing the reason for the failure\n\no schema-location: A JSON pointer URI fragment identifier indicating\n where in the JSON schema the validation failed (see Note following\n this list)\n\no document-location: A JSON pointer URI fragment identifier indicating\n where in the JSON document the validation failed (see Note following\n this list)\n\no schema-failed-keyword: A string containing the name of the keyword or\n property in the JSON schema that was violated\n\n*Note*:\n\nJSON pointer URI fragment identifiers are defined in RFC 6901 -\nJavaScript Object Notation (JSON) Pointer\n(https://tools.ietf.org/html/rfc6901#page-5). (These are not the same\nas the JSON path notation used by JSON_EXTRACT() and other MySQL JSON\nfunctions.) In this notation, # represents the entire document, and\n#/myprop represents the portion of the document included in the\ntop-level property named myprop. See the specification just cited and\nthe examples shown later in this section for more information.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-validation-functions.html +[JSON_SEARCH] +declaration=json_doc, one_or_all, search_str[, escape_char[, path] ...] +category=MBR Functions +description=Returns the path to the given string within a JSON document. Returns\nNULL if any of the json_doc, search_str, or path arguments are NULL; no\npath exists within the document; or search_str is not found. An error\noccurs if the json_doc argument is not a valid JSON document, any path\nargument is not a valid path expression, one_or_all is not 'one' or\n'all', or escape_char is not a constant expression.\n\nThe one_or_all argument affects the search as follows:\n\no 'one': The search terminates after the first match and returns one\n path string. It is undefined which match is considered first.\n\no 'all': The search returns all matching path strings such that no\n duplicate paths are included. If there are multiple strings, they are\n autowrapped as an array. The order of the array elements is\n undefined.\n\nWithin the search_str search string argument, the % and _ characters\nwork as for the LIKE operator: % matches any number of characters\n(including zero characters), and _ matches exactly one character.\n\nTo specify a literal % or _ character in the search string, precede it\nby the escape character. The default is \ if the escape_char argument\nis missing or NULL. Otherwise, escape_char must be a constant that is\nempty or one character.\n\nFor more information about matching and escape character behavior, see\nthe description of LIKE in\nhttps://dev.mysql.com/doc/refman/8.3/en/string-comparison-functions.html\n. For escape character handling, a difference from the LIKE behavior\nis that the escape character for JSON_SEARCH() must evaluate to a\nconstant at compile time, not just at execution time. For example, if\nJSON_SEARCH() is used in a prepared statement and the escape_char\nargument is supplied using a ? parameter, the parameter value might be\nconstant at execution time, but is not at compile time.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html +[JSON_SET] +declaration=json_doc, path, val[, path, val] ... +category=MBR Functions +description=Inserts or updates data in a JSON document and returns the result.\nReturns NULL if json_doc or path is NULL, or if path, when given, does\nnot locate an object. Otherwise, an error occurs if the json_doc\nargument is not a valid JSON document or any path argument is not a\nvalid path expression or contains a * or ** wildcard.\n\nThe path-value pairs are evaluated left to right. The document produced\nby evaluating one pair becomes the new value against which the next\npair is evaluated.\n\nA path-value pair for an existing path in the document overwrites the\nexisting document value with the new value. A path-value pair for a\nnonexisting path in the document adds the value to the document if the\npath identifies one of these types of values:\n\no A member not present in an existing object. The member is added to\n the object and associated with the new value.\n\no A position past the end of an existing array. The array is extended\n with the new value. If the existing value is not an array, it is\n autowrapped as an array, then extended with the new value.\n\nOtherwise, a path-value pair for a nonexisting path in the document is\nignored and has no effect.\n\nThe optimizer can perform a partial, in-place update of a JSON column\ninstead of removing the old document and writing the new document in\nits entirety to the column. This optimization can be performed for an\nupdate statement that uses the JSON_SET() function and meets the\nconditions outlined in\nhttps://dev.mysql.com/doc/refman/8.3/en/json.html#json-partial-updates.\n\nThe JSON_SET(), JSON_INSERT(), and JSON_REPLACE() functions are\nrelated:\n\no JSON_SET() replaces existing values and adds nonexisting values.\n\no JSON_INSERT() inserts values without replacing existing values.\n\no JSON_REPLACE() replaces only existing values.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html +[JSON_STORAGE_FREE] +declaration=json_val +category=MBR Functions +description=For a JSON column value, this function shows how much storage space was\nfreed in its binary representation after it was updated in place using\nJSON_SET(), JSON_REPLACE(), or JSON_REMOVE(). The argument can also be\na valid JSON document or a string which can be parsed as one---either\nas a literal value or as the value of a user variable---in which case\nthe function returns 0. It returns a positive, nonzero value if the\nargument is a JSON column value which has been updated as described\npreviously, such that its binary representation takes up less space\nthan it did prior to the update. For a JSON column which has been\nupdated such that its binary representation is the same as or larger\nthan before, or if the update was not able to take advantage of a\npartial update, it returns 0; it returns NULL if the argument is NULL.\n\nIf json_val is not NULL, and neither is a valid JSON document nor can\nbe successfully parsed as one, an error results.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-utility-functions.html +[JSON_STORAGE_SIZE] +declaration=json_val +category=MBR Functions +description=This function returns the number of bytes used to store the binary\nrepresentation of a JSON document. When the argument is a JSON column,\nthis is the space used to store the JSON document as it was inserted\ninto the column, prior to any partial updates that may have been\nperformed on it afterwards. json_val must be a valid JSON document or a\nstring which can be parsed as one. In the case where it is string, the\nfunction returns the amount of storage space in the JSON binary\nrepresentation that is created by parsing the string as JSON and\nconverting it to binary. It returns NULL if the argument is NULL.\n\nAn error results when json_val is not NULL, and is not---or cannot be\nsuccessfully parsed as---a JSON document.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-utility-functions.html +[JSON_TABLE] +declaration=expr, path COLUMNS (column_list +category=MBR Functions +description=Extracts data from a JSON document and returns it as a relational table\nhaving the specified columns. The complete syntax for this function is\nshown here:\n\nJSON_TABLE(\n expr,\n path COLUMNS (column_list)\n) [AS] alias\n\ncolumn_list:\n column[, column][, ...]\n\ncolumn:\n name FOR ORDINALITY\n | name type PATH string path [on_empty] [on_error]\n | name type EXISTS PATH string path\n | NESTED [PATH] path COLUMNS (column_list)\n\non_empty:\n {NULL | DEFAULT json_string | ERROR} ON EMPTY\n\non_error:\n {NULL | DEFAULT json_string | ERROR} ON ERROR\n\nexpr: This is an expression that returns JSON data. This can be a\nconstant ('{"a":1}'), a column (t1.json_data, given table t1 specified\nprior to JSON_TABLE() in the FROM clause), or a function call\n(JSON_EXTRACT(t1.json_data,'$.post.comments')).\n\npath: A JSON path expression, which is applied to the data source. We\nrefer to the JSON value matching the path as the row source; this is\nused to generate a row of relational data. The COLUMNS clause evaluates\nthe row source, finds specific JSON values within the row source, and\nreturns those JSON values as SQL values in individual columns of a row\nof relational data.\n\nThe alias is required. The usual rules for table aliases apply (see\nhttps://dev.mysql.com/doc/refman/8.3/en/identifiers.html).\n\nThis function compares column names in case-insensitive fashion.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-table-functions.html +[JSON_TYPE] +declaration=json_val +category=MBR Functions +description=Returns a utf8mb4 string indicating the type of a JSON value. This can\nbe an object, an array, or a scalar type, as shown here:\n\nmysql> SET @j = '{"a": [10, true]}';\nmysql> SELECT JSON_TYPE(@j);\n+---------------+\n| JSON_TYPE(@j) |\n+---------------+\n| OBJECT |\n+---------------+\nmysql> SELECT JSON_TYPE(JSON_EXTRACT(@j, '$.a'));\n+------------------------------------+\n| JSON_TYPE(JSON_EXTRACT(@j, '$.a')) |\n+------------------------------------+\n| ARRAY |\n+------------------------------------+\nmysql> SELECT JSON_TYPE(JSON_EXTRACT(@j, '$.a[0]'));\n+---------------------------------------+\n| JSON_TYPE(JSON_EXTRACT(@j, '$.a[0]')) |\n+---------------------------------------+\n| INTEGER |\n+---------------------------------------+\nmysql> SELECT JSON_TYPE(JSON_EXTRACT(@j, '$.a[1]'));\n+---------------------------------------+\n| JSON_TYPE(JSON_EXTRACT(@j, '$.a[1]')) |\n+---------------------------------------+\n| BOOLEAN |\n+---------------------------------------+\n\nJSON_TYPE() returns NULL if the argument is NULL:\n\nmysql> SELECT JSON_TYPE(NULL);\n+-----------------+\n| JSON_TYPE(NULL) |\n+-----------------+\n| NULL |\n+-----------------+\n\nAn error occurs if the argument is not a valid JSON value:\n\nmysql> SELECT JSON_TYPE(1);\nERROR 3146 (22032): Invalid data type for JSON data in argument 1\nto function json_type; a JSON string or JSON type is required.\n\nFor a non-NULL, non-error result, the following list describes the\npossible JSON_TYPE() return values:\n\no Purely JSON types:\n\n o OBJECT: JSON objects\n ... +[JSON_UNQUOTE] +declaration=json_val +category=MBR Functions +description=Unquotes JSON value and returns the result as a utf8mb4 string. Returns\nNULL if the argument is NULL. An error occurs if the value starts and\nends with double quotes but is not a valid JSON string literal.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html +[JSON_VALID] +declaration=val +category=MBR Functions +description=Returns 0 or 1 to indicate whether a value is valid JSON. Returns NULL\nif the argument is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-attribute-functions.html +[JSON_VALUE] +declaration=json_doc, path +category=MBR Functions +description=Extracts a value from a JSON document at the path given in the\nspecified document, and returns the extracted value, optionally\nconverting it to a desired type. The complete syntax is shown here:\n\nJSON_VALUE(json_doc, path [RETURNING type] [on_empty] [on_error])\n\non_empty:\n {NULL | ERROR | DEFAULT value} ON EMPTY\n\non_error:\n {NULL | ERROR | DEFAULT value} ON ERROR\n\njson_doc is a valid JSON document. If this is NULL, the function\nreturns NULL.\n\npath is a JSON path pointing to a location in the document. This must\nbe a string literal value.\n\ntype is one of the following data types:\n\no FLOAT\n\no DOUBLE\n\no DECIMAL\n\no SIGNED\n\no UNSIGNED\n\no DATE\n\no TIME\n\no DATETIME\n\no YEAR\n\n YEAR values of one or two digits are not supported.\n\no CHAR\n\no JSON\n\nThe types just listed are the same as the (non-array) types supported\nby the CAST() function.\n\nIf not specified by a RETURNING clause, the JSON_VALUE() function's\nreturn type is VARCHAR(512). When no character set is specified for the\nreturn type, JSON_VALUE() uses utf8mb4 with the binary collation, which\n ... +[LAG] +declaration=expr [, N[, default]] +category=Window Functions +description=Returns the value of expr from the row that lags (precedes) the current\nrow by N rows within its partition. If there is no such row, the return\nvalue is default. For example, if N is 3, the return value is default\nfor the first three rows. If N or default are missing, the defaults are\n1 and NULL, respectively.\n\nN must be a literal nonnegative integer. If N is 0, expr is evaluated\nfor the current row.\n\nN cannot be NULL, and must be an integer in the range 0 to 263,\ninclusive, in any of the following forms:\n\no an unsigned integer constant literal\n\no a positional parameter marker (?)\n\no a user-defined variable\n\no a local variable in a stored routine\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\nnull_treatment is as described in the section introduction.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[LAST_DAY] +declaration=date +category=Date and Time Functions +description=Takes a date or datetime value and returns the corresponding value for\nthe last day of the month. Returns NULL if the argument is invalid or\nNULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[LAST_INSERT_ID] +declaration= +category=Information Functions +description=With no argument, LAST_INSERT_ID() returns a BIGINT UNSIGNED (64-bit)\nvalue representing the first automatically generated value successfully\ninserted for an AUTO_INCREMENT column as a result of the most recently\nexecuted INSERT statement. The value of LAST_INSERT_ID() remains\nunchanged if no rows are successfully inserted.\n\nWith an argument, LAST_INSERT_ID() returns an unsigned integer, or NULL\nif the argument is NULL.\n\nFor example, after inserting a row that generates an AUTO_INCREMENT\nvalue, you can get the value like this:\n\nmysql> SELECT LAST_INSERT_ID();\n -> 195\n\nThe currently executing statement does not affect the value of\nLAST_INSERT_ID(). Suppose that you generate an AUTO_INCREMENT value\nwith one statement, and then refer to LAST_INSERT_ID() in a\nmultiple-row INSERT statement that inserts rows into a table with its\nown AUTO_INCREMENT column. The value of LAST_INSERT_ID() remains stable\nin the second statement; its value for the second and later rows is not\naffected by the earlier row insertions. (You should be aware that, if\nyou mix references to LAST_INSERT_ID() and LAST_INSERT_ID(expr), the\neffect is undefined.)\n\nIf the previous statement returned an error, the value of\nLAST_INSERT_ID() is undefined. For transactional tables, if the\nstatement is rolled back due to an error, the value of LAST_INSERT_ID()\nis left undefined. For manual ROLLBACK, the value of LAST_INSERT_ID()\nis not restored to that before the transaction; it remains as it was at\nthe point of the ROLLBACK.\n\nWithin the body of a stored routine (procedure or function) or a\ntrigger, the value of LAST_INSERT_ID() changes the same way as for\nstatements executed outside the body of these kinds of objects. The\neffect of a stored routine or trigger upon the value of\nLAST_INSERT_ID() that is seen by following statements depends on the\nkind of routine:\n\no If a stored procedure executes statements that change the value of\n LAST_INSERT_ID(), the changed value is seen by statements that follow\n the procedure call.\n\no For stored functions and triggers that change the value, the value is\n restored when the function or trigger ends, so statements coming\n after it do not see a changed value.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[LAST_VALUE] +declaration=expr +category=Window Functions +description=Returns the value of expr from the last row of the window frame.\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\nnull_treatment is as described in the section introduction.\n\nFor an example, see the FIRST_VALUE() function description.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[LCASE] +declaration=str +category=String Functions +description=LCASE() is a synonym for LOWER().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[LEAD] +declaration=expr [, N[, default]] +category=Window Functions +description=Returns the value of expr from the row that leads (follows) the current\nrow by N rows within its partition. If there is no such row, the return\nvalue is default. For example, if N is 3, the return value is default\nfor the last three rows. If N or default are missing, the defaults are\n1 and NULL, respectively.\n\nN must be a literal nonnegative integer. If N is 0, expr is evaluated\nfor the current row.\n\nN cannot be NULL, and must be an integer in the range 0 to 263,\ninclusive, in any of the following forms:\n\no an unsigned integer constant literal\n\no a positional parameter marker (?)\n\no a user-defined variable\n\no a local variable in a stored routine\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\nnull_treatment is as described in the section introduction.\n\nFor an example, see the LAG() function description.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[LEAST] +declaration=value1,value2,... +category=Comparison Operators +description=With two or more arguments, returns the smallest (minimum-valued)\nargument. The arguments are compared using the following rules:\n\no If any argument is NULL, the result is NULL. No comparison is needed.\n\no If all arguments are integer-valued, they are compared as integers.\n\no If at least one argument is double precision, they are compared as\n double-precision values. Otherwise, if at least one argument is a\n DECIMAL value, they are compared as DECIMAL values.\n\no If the arguments comprise a mix of numbers and strings, they are\n compared as strings.\n\no If any argument is a nonbinary (character) string, the arguments are\n compared as nonbinary strings.\n\no In all other cases, the arguments are compared as binary strings.\n\nThe return type of LEAST() is the aggregated type of the comparison\nargument types.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html +[LEFT] +declaration=str,len +category=String Functions +description=Returns the leftmost len characters from the string str, or NULL if any\nargument is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[LENGTH] +declaration=str +category=String Functions +description=Returns the length of the string str, measured in bytes. A multibyte\ncharacter counts as multiple bytes. This means that for a string\ncontaining five 2-byte characters, LENGTH() returns 10, whereas\nCHAR_LENGTH() returns 5. Returns NULL if str is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[LINESTRING] +declaration=pt [, pt] ... +category=Geometry Constructors +description=Constructs a LineString value from a number of Point or WKB Point\narguments. If the number of arguments is less than two, the return\nvalue is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html +[LN] +declaration=X +category=Numeric Functions +description=Returns the natural logarithm of X; that is, the base-e logarithm of X.\nIf X is less than or equal to 0.0E0, the function returns NULL and a\nwarning "Invalid argument for logarithm" is reported. Returns NULL if X\nis NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[LOAD_FILE] +declaration=file_name +category=String Functions +description=Reads the file and returns the file contents as a string. To use this\nfunction, the file must be located on the server host, you must specify\nthe full path name to the file, and you must have the FILE privilege.\nThe file must be readable by the server and its size less than\nmax_allowed_packet bytes. If the secure_file_priv system variable is\nset to a nonempty directory name, the file to be loaded must be located\nin that directory.\n\nIf the file does not exist or cannot be read because one of the\npreceding conditions is not satisfied, the function returns NULL.\n\nThe character_set_filesystem system variable controls interpretation of\nfile names that are given as literal strings.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[LOCALTIME] +declaration=[fsp] +category=Date and Time Functions +description=LOCALTIME and LOCALTIME() are synonyms for NOW().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[LOCALTIMESTAMP] +declaration=[fsp] +category=Date and Time Functions +description=LOCALTIMESTAMP and LOCALTIMESTAMP() are synonyms for NOW().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[LOCATE] +declaration=substr,str +category=String Functions +description=The first syntax returns the position of the first occurrence of\nsubstring substr in string str. The second syntax returns the position\nof the first occurrence of substring substr in string str, starting at\nposition pos. Returns 0 if substr is not in str. Returns NULL if any\nargument is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[LOG] +declaration=X +category=Numeric Functions +description=If called with one parameter, this function returns the natural\nlogarithm of X. If X is less than or equal to 0.0E0, the function\nreturns NULL and a warning "Invalid argument for logarithm" is\nreported. Returns NULL if X or B is NULL.\n\nThe inverse of this function (when called with a single argument) is\nthe EXP() function.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[LOG10] +declaration=X +category=Numeric Functions +description=Returns the base-10 logarithm of X. If X is less than or equal to\n0.0E0, the function returns NULL and a warning "Invalid argument for\nlogarithm" is reported. Returns NULL if X is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[LOG2] +declaration=X +category=Numeric Functions +description=Returns the base-2 logarithm of X. If X is less than or equal to 0.0E0,\nthe function returns NULL and a warning "Invalid argument for\nlogarithm" is reported. Returns NULL if X is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[LOWER] +declaration=str +category=String Functions +description=Returns the string str with all characters changed to lowercase\naccording to the current character set mapping, or NULL if str is NULL.\nThe default character set is utf8mb4.\n\nmysql> SELECT LOWER('QUADRATICALLY');\n -> 'quadratically'\n\nLOWER() (and UPPER()) are ineffective when applied to binary strings\n(BINARY, VARBINARY, BLOB). To perform lettercase conversion of a binary\nstring, first convert it to a nonbinary string using a character set\nappropriate for the data stored in the string:\n\nmysql> SET @str = BINARY 'New York';\nmysql> SELECT LOWER(@str), LOWER(CONVERT(@str USING utf8mb4));\n+-------------+------------------------------------+\n| LOWER(@str) | LOWER(CONVERT(@str USING utf8mb4)) |\n+-------------+------------------------------------+\n| New York | new york |\n+-------------+------------------------------------+\n\nFor collations of Unicode character sets, LOWER() and UPPER() work\naccording to the Unicode Collation Algorithm (UCA) version in the\ncollation name, if there is one, and UCA 4.0.0 if no version is\nspecified. For example, utf8mb4_0900_ai_ci and utf8mb3_unicode_520_ci\nwork according to UCA 9.0.0 and 5.2.0, respectively, whereas\nutf8mb3_unicode_ci works according to UCA 4.0.0. See\nhttps://dev.mysql.com/doc/refman/8.3/en/charset-unicode-sets.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[LPAD] +declaration=str,len,padstr +category=String Functions +description=Returns the string str, left-padded with the string padstr to a length\nof len characters. If str is longer than len, the return value is\nshortened to len characters.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[LTRIM] +declaration=str +category=String Functions +description=Returns the string str with leading space characters removed. Returns\nNULL if str is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[MAKEDATE] +declaration=year,dayofyear +category=Date and Time Functions +description=Returns a date, given year and day-of-year values. dayofyear must be\ngreater than 0 or the result is NULL. The result is also NULL if either\nargument is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[MAKETIME] +declaration=hour,minute,second +category=Date and Time Functions +description=Returns a time value calculated from the hour, minute, and second\narguments. Returns NULL if any of its arguments are NULL.\n\nThe second argument can have a fractional part.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[MAKE_SET] +declaration=bits,str1,str2,... +category=String Functions +description=Returns a set value (a string containing substrings separated by ,\ncharacters) consisting of the strings that have the corresponding bit\nin bits set. str1 corresponds to bit 0, str2 to bit 1, and so on. NULL\nvalues in str1, str2, ... are not appended to the result.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[MASTER_POS_WAIT] +declaration=log_name,log_pos[,timeout][,channel] +category=GTID +description=Deprecated alias for SOURCE_POS_WAIT().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/replication-functions-synchronization.html +[MAX] +declaration=[DISTINCT] expr +category=Aggregate Functions and Modifiers +description=Returns the maximum value of expr. MAX() may take a string argument; in\nsuch cases, it returns the maximum string value. See\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql-indexes.html. The\nDISTINCT keyword can be used to find the maximum of the distinct values\nof expr, however, this produces the same result as omitting DISTINCT.\n\nIf there are no matching rows, or if expr is NULL, MAX() returns NULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html; it\ncannot be used with DISTINCT.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[MBRCONTAINS] +declaration=g1, g2 +category=MBR Functions +description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1\ncontains the minimum bounding rectangle of g2. This tests the opposite\nrelationship as MBRWithin().\n\nMBRContains() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html +[MBRCOVEREDBY] +declaration=g1, g2 +category=MBR Functions +description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1\nis covered by the minimum bounding rectangle of g2. This tests the\nopposite relationship as MBRCovers().\n\nMBRCoveredBy() handles its arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html +[MBRCOVERS] +declaration=g1, g2 +category=MBR Functions +description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1\ncovers the minimum bounding rectangle of g2. This tests the opposite\nrelationship as MBRCoveredBy(). See the description of MBRCoveredBy()\nfor examples.\n\nMBRCovers() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html +[MBRDISJOINT] +declaration=g1, g2 +category=MBR Functions +description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of\nthe two geometries g1 and g2 are disjoint (do not intersect).\n\nMBRDisjoint() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html +[MBREQUALS] +declaration=g1, g2 +category=MBR Functions +description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of\nthe two geometries g1 and g2 are the same.\n\nMBREquals() handles its arguments as described in the introduction to\nthis section, except that it does not return NULL for empty geometry\narguments.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html +[MBRINTERSECTS] +declaration=g1, g2 +category=MBR Functions +description=Returns 1 or 0 to indicate whether the minimum bounding rectangles of\nthe two geometries g1 and g2 intersect.\n\nMBRIntersects() handles its arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html +[MBROVERLAPS] +declaration=g1, g2 +category=MBR Functions +description=Two geometries spatially overlap if they intersect and their\nintersection results in a geometry of the same dimension but not equal\nto either of the given geometries.\n\nThis function returns 1 or 0 to indicate whether the minimum bounding\nrectangles of the two geometries g1 and g2 overlap.\n\nMBROverlaps() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html +[MBRTOUCHES] +declaration=g1, g2 +category=MBR Functions +description=Two geometries spatially touch if their interiors do not intersect, but\nthe boundary of one of the geometries intersects either the boundary or\nthe interior of the other.\n\nThis function returns 1 or 0 to indicate whether the minimum bounding\nrectangles of the two geometries g1 and g2 touch.\n\nMBRTouches() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html +[MBRWITHIN] +declaration=g1, g2 +category=MBR Functions +description=Returns 1 or 0 to indicate whether the minimum bounding rectangle of g1\nis within the minimum bounding rectangle of g2. This tests the opposite\nrelationship as MBRContains().\n\nMBRWithin() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html +[MD5] +declaration=str +category=Encryption Functions +description=Calculates an MD5 128-bit checksum for the string. The value is\nreturned as a string of 32 hexadecimal digits, or NULL if the argument\nwas NULL. The return value can, for example, be used as a hash key. See\nthe notes at the beginning of this section about storing hash values\nefficiently.\n\nThe return value is a string in the connection character set.\n\nIf FIPS mode is enabled, MD5() returns NULL. See\nhttps://dev.mysql.com/doc/refman/8.3/en/fips-mode.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[MEDIUMINT] +declaration=M +category=Data Types +description=A medium-sized integer. The signed range is -8388608 to 8388607. The\nunsigned range is 0 to 16777215.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[MICROSECOND] +declaration=expr +category=Date and Time Functions +description=Returns the microseconds from the time or datetime expression expr as a\nnumber in the range from 0 to 999999. Returns NULL if expr is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[MID] +declaration=str,pos,len +category=String Functions +description=MID(str,pos,len) is a synonym for SUBSTRING(str,pos,len).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[MIN] +declaration=[DISTINCT] expr +category=Aggregate Functions and Modifiers +description=Returns the minimum value of expr. MIN() may take a string argument; in\nsuch cases, it returns the minimum string value. See\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql-indexes.html. The\nDISTINCT keyword can be used to find the minimum of the distinct values\nof expr, however, this produces the same result as omitting DISTINCT.\n\nIf there are no matching rows, or if expr is NULL, MIN() returns NULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html; it\ncannot be used with DISTINCT.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[MINUTE] +declaration=time +category=Date and Time Functions +description=Returns the minute for time, in the range 0 to 59, or NULL if time is\nNULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[MOD] +declaration=N,M +category=Numeric Functions +description=Modulo operation. Returns the remainder of N divided by M. Returns NULL\nif M or N is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[MONTH] +declaration=date +category=Date and Time Functions +description=Returns the month for date, in the range 1 to 12 for January to\nDecember, or 0 for dates such as '0000-00-00' or '2008-00-00' that have\na zero month part. Returns NULL if date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[MONTHNAME] +declaration=date +category=Date and Time Functions +description=Returns the full name of the month for date. The language used for the\nname is controlled by the value of the lc_time_names system variable\n(https://dev.mysql.com/doc/refman/8.3/en/locale-support.html). Returns\nNULL if date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[MULTILINESTRING] +declaration=ls [, ls] ... +category=Geometry Constructors +description=Constructs a MultiLineString value using LineString or WKB LineString\narguments.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html +[MULTIPOINT] +declaration=pt [, pt2] ... +category=Geometry Constructors +description=Constructs a MultiPoint value using Point or WKB Point arguments.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html +[MULTIPOLYGON] +declaration=poly [, poly] ... +category=Geometry Constructors +description=Constructs a MultiPolygon value from a set of Polygon or WKB Polygon\narguments.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html +[NAME_CONST] +declaration=name,value +category=Miscellaneous Functions +description=Returns the given value. When used to produce a result set column,\nNAME_CONST() causes the column to have the given name. The arguments\nshould be constants.\n\nmysql> SELECT NAME_CONST('myname', 14);\n+--------+\n| myname |\n+--------+\n| 14 |\n+--------+\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[NOW] +declaration=[fsp] +category=Date and Time Functions +description=Returns the current date and time as a value in 'YYYY-MM-DD hh:mm:ss'\nor YYYYMMDDhhmmss format, depending on whether the function is used in\nstring or numeric context. The value is expressed in the session time\nzone.\n\nIf the fsp argument is given to specify a fractional seconds precision\nfrom 0 to 6, the return value includes a fractional seconds part of\nthat many digits.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[NTH_VALUE] +declaration=expr, N +category=Window Functions +description=Returns the value of expr from the N-th row of the window frame. If\nthere is no such row, the return value is NULL.\n\nN must be a literal positive integer.\n\nfrom_first_last is part of the SQL standard, but the MySQL\nimplementation permits only FROM FIRST (which is also the default).\nThis means that calculations begin at the first row of the window. FROM\nLAST is parsed, but produces an error. To obtain the same effect as\nFROM LAST (begin calculations at the last row of the window), use ORDER\nBY to sort in reverse order.\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\nnull_treatment is as described in the section introduction.\n\nFor an example, see the FIRST_VALUE() function description.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[NTILE] +declaration=N +category=Window Functions +description=Divides a partition into N groups (buckets), assigns each row in the\npartition its bucket number, and returns the bucket number of the\ncurrent row within its partition. For example, if N is 4, NTILE()\ndivides rows into four buckets. If N is 100, NTILE() divides rows into\n100 buckets.\n\nN must be a literal positive integer. Bucket number return values range\nfrom 1 to N.\n\nN cannot be NULL, and must be an integer in the range 0 to 263,\ninclusive, in any of the following forms:\n\no an unsigned integer constant literal\n\no a positional parameter marker (?)\n\no a user-defined variable\n\no a local variable in a stored routine\n\nThis function should be used with ORDER BY to sort partition rows into\nthe desired order.\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[NULLIF] +declaration=expr1,expr2 +category=Flow Control Functions +description=Returns NULL if expr1 = expr2 is true, otherwise returns expr1. This is\nthe same as CASE WHEN expr1 = expr2 THEN NULL ELSE expr1 END.\n\nThe return value has the same type as the first argument.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/flow-control-functions.html +[OCT] +declaration=N +category=String Functions +description=Returns a string representation of the octal value of N, where N is a\nlonglong (BIGINT) number. This is equivalent to CONV(N,10,8). Returns\nNULL if N is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[OCTET_LENGTH] +declaration=str +category=String Functions +description=OCTET_LENGTH() is a synonym for LENGTH().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[ORD] +declaration=str +category=String Functions +description=If the leftmost character of the string str is a multibyte character,\nreturns the code for that character, calculated from the numeric values\nof its constituent bytes using this formula:\n\n (1st byte code)\n+ (2nd byte code * 256)\n+ (3rd byte code * 256^2) ...\n\nIf the leftmost character is not a multibyte character, ORD() returns\nthe same value as the ASCII() function. The function returns NULL if\nstr is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[PERCENT_RANK] +declaration= +category=Window Functions +description=Returns the percentage of partition values less than the value in the\ncurrent row, excluding the highest value. Return values range from 0 to\n1 and represent the row relative rank, calculated as the result of this\nformula, where rank is the row rank and rows is the number of partition\nrows:\n\n(rank - 1) / (rows - 1)\n\nThis function should be used with ORDER BY to sort partition rows into\nthe desired order. Without ORDER BY, all rows are peers.\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nFor an example, see the CUME_DIST() function description.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[PERIOD_ADD] +declaration=P,N +category=Date and Time Functions +description=Adds N months to period P (in the format YYMM or YYYYMM). Returns a\nvalue in the format YYYYMM.\n\n*Note*:\n\nThe period argument P is not a date value.\n\nThis function returns NULL if P or N is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[PERIOD_DIFF] +declaration=P1,P2 +category=Date and Time Functions +description=Returns the number of months between periods P1 and P2. P1 and P2\nshould be in the format YYMM or YYYYMM. Note that the period arguments\nP1 and P2 are not date values.\n\nThis function returns NULL if P1 or P2 is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[PI] +declaration= +category=Numeric Functions +description=Returns the value of π (pi). The default number of decimal places\ndisplayed is seven, but MySQL uses the full double-precision value\ninternally.\n\nBecause the return value of this function is a double-precision value,\nits exact representation may vary between platforms or implementations.\nThis also applies to any expressions making use of PI(). See\nhttps://dev.mysql.com/doc/refman/8.3/en/floating-point-types.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[POINT] +declaration=x, y +category=Geometry Constructors +description=Constructs a Point using its coordinates.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html +[POLYGON] +declaration=ls [, ls] ... +category=Geometry Constructors +description=Constructs a Polygon value from a number of LineString or WKB\nLineString arguments. If any argument does not represent a LinearRing\n(that is, not a closed and simple LineString), the return value is\nNULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html +[POSITION] +declaration=substr IN str +category=String Functions +description=POSITION(substr IN str) is a synonym for LOCATE(substr,str).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[POW] +declaration=X,Y +category=Numeric Functions +description=Returns the value of X raised to the power of Y. Returns NULL if X or Y\nis NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[POWER] +declaration=X,Y +category=Numeric Functions +description=This is a synonym for POW().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[PS_CURRENT_THREAD_ID] +declaration= +category=Performance Schema Functions +description=Returns a BIGINT UNSIGNED value representing the Performance Schema\nthread ID assigned to the current connection.\n\nThe thread ID return value is a value of the type given in the\nTHREAD_ID column of Performance Schema tables.\n\nPerformance Schema configuration affects PS_CURRENT_THREAD_ID() the\nsame way as for PS_THREAD_ID(). For details, see the description of\nthat function.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/performance-schema-functions.html +[PS_THREAD_ID] +declaration=connection_id +category=Performance Schema Functions +description=Given a connection ID, returns a BIGINT UNSIGNED value representing the\nPerformance Schema thread ID assigned to the connection ID, or NULL if\nno thread ID exists for the connection ID. The latter can occur for\nthreads that are not instrumented, or if connection_id is NULL.\n\nThe connection ID argument is a value of the type given in the\nPROCESSLIST_ID column of the Performance Schema threads table or the Id\ncolumn of SHOW PROCESSLIST output.\n\nThe thread ID return value is a value of the type given in the\nTHREAD_ID column of Performance Schema tables.\n\nPerformance Schema configuration affects PS_THREAD_ID() operation as\nfollows. (These remarks also apply to PS_CURRENT_THREAD_ID().)\n\no Disabling the thread_instrumentation consumer disables statistics\n from being collected and aggregated at the thread level, but has no\n effect on PS_THREAD_ID().\n\no If performance_schema_max_thread_instances is not 0, the Performance\n Schema allocates memory for thread statistics and assigns an internal\n ID to each thread for which instance memory is available. If there\n are threads for which instance memory is not available,\n PS_THREAD_ID() returns NULL; in this case,\n Performance_schema_thread_instances_lost is nonzero.\n\no If performance_schema_max_thread_instances is 0, the Performance\n Schema allocates no thread memory and PS_THREAD_ID() returns NULL.\n\no If the Performance Schema itself is disabled, PS_THREAD_ID() produces\n an error.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/performance-schema-functions.html +[QUARTER] +declaration=date +category=Date and Time Functions +description=Returns the quarter of the year for date, in the range 1 to 4, or NULL\nif date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[QUOTE] +declaration=str +category=String Functions +description=Quotes a string to produce a result that can be used as a properly\nescaped data value in an SQL statement. The string is returned enclosed\nby single quotation marks and with each instance of backslash (\),\nsingle quote ('), ASCII NUL, and Control+Z preceded by a backslash. If\nthe argument is NULL, the return value is the word "NULL" without\nenclosing single quotation marks.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[RADIANS] +declaration=X +category=Numeric Functions +description=Returns the argument X, converted from degrees to radians. (Note that\nπ radians equals 180 degrees.) Returns NULL if X is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[RAND] +declaration=[N] +category=Numeric Functions +description=Returns a random floating-point value v in the range 0 <= v < 1.0. To\nobtain a random integer R in the range i <= R < j, use the expression\nFLOOR(i + RAND() * (j − i)). For example, to obtain a random integer\nin the range the range 7 <= R < 12, use the following statement:\n\nSELECT FLOOR(7 + (RAND() * 5));\n\nIf an integer argument N is specified, it is used as the seed value:\n\no With a constant initializer argument, the seed is initialized once\n when the statement is prepared, prior to execution.\n\no With a nonconstant initializer argument (such as a column name), the\n seed is initialized with the value for each invocation of RAND().\n\nOne implication of this behavior is that for equal argument values,\nRAND(N) returns the same value each time, and thus produces a\nrepeatable sequence of column values. In the following example, the\nsequence of values produced by RAND(3) is the same both places it\noccurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[RANDOM_BYTES] +declaration=len +category=Encryption Functions +description=This function returns a binary string of len random bytes generated\nusing the random number generator of the SSL library. Permitted values\nof len range from 1 to 1024. For values outside that range, an error\noccurs. Returns NULL if len is NULL.\n\nRANDOM_BYTES() can be used to provide the initialization vector for the\nAES_DECRYPT() and AES_ENCRYPT() functions. For use in that context, len\nmust be at least 16. Larger values are permitted, but bytes in excess\nof 16 are ignored.\n\nRANDOM_BYTES() generates a random value, which makes its result\nnondeterministic. Consequently, statements that use this function are\nunsafe for statement-based replication.\n\nIf RANDOM_BYTES() is invoked from within the mysql client, binary\nstrings display using hexadecimal notation, depending on the value of\nthe --binary-as-hex. For more information about that option, see\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[RANK] +declaration= +category=Window Functions +description=Returns the rank of the current row within its partition, with gaps.\nPeers are considered ties and receive the same rank. This function does\nnot assign consecutive ranks to peer groups if groups of size greater\nthan one exist; the result is noncontiguous rank numbers.\n\nThis function should be used with ORDER BY to sort partition rows into\nthe desired order. Without ORDER BY, all rows are peers.\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[REGEXP_INSTR] +declaration=expr, pat[, pos[, occurrence[, return_option[, match_type]]]] +category=String Functions +description=Returns the starting index of the substring of the string expr that\nmatches the regular expression specified by the pattern pat, 0 if there\nis no match. If expr or pat is NULL, the return value is NULL.\nCharacter indexes begin at 1.\n\nREGEXP_INSTR() takes these optional arguments:\n\no pos: The position in expr at which to start the search. If omitted,\n the default is 1.\n\no occurrence: Which occurrence of a match to search for. If omitted,\n the default is 1.\n\no return_option: Which type of position to return. If this value is 0,\n REGEXP_INSTR() returns the position of the matched substring's first\n character. If this value is 1, REGEXP_INSTR() returns the position\n following the matched substring. If omitted, the default is 0.\n\no match_type: A string that specifies how to perform matching. The\n meaning is as described for REGEXP_LIKE().\n\nFor additional information about how matching occurs, see the\ndescription for REGEXP_LIKE().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/regexp.html +[REGEXP_LIKE] +declaration=expr, pat[, match_type] +category=String Functions +description=Returns 1 if the string expr matches the regular expression specified\nby the pattern pat, 0 otherwise. If expr or pat is NULL, the return\nvalue is NULL.\n\nThe pattern can be an extended regular expression, the syntax for which\nis discussed in\nhttps://dev.mysql.com/doc/refman/8.3/en/regexp.html#regexp-syntax. The\npattern need not be a literal string. For example, it can be specified\nas a string expression or table column.\n\nThe optional match_type argument is a string that may contain any or\nall the following characters specifying how to perform matching:\n\no c: Case-sensitive matching.\n\no i: Case-insensitive matching.\n\no m: Multiple-line mode. Recognize line terminators within the string.\n The default behavior is to match line terminators only at the start\n and end of the string expression.\n\no n: The . character matches line terminators. The default is for .\n matching to stop at the end of a line.\n\no u: Unix-only line endings. Only the newline character is recognized\n as a line ending by the ., ^, and $ match operators.\n\nIf characters specifying contradictory options are specified within\nmatch_type, the rightmost one takes precedence.\n\nBy default, regular expression operations use the character set and\ncollation of the expr and pat arguments when deciding the type of a\ncharacter and performing the comparison. If the arguments have\ndifferent character sets or collations, coercibility rules apply as\ndescribed in\nhttps://dev.mysql.com/doc/refman/8.3/en/charset-collation-coercibility.\nhtml. Arguments may be specified with explicit collation indicators to\nchange comparison behavior.\n\nmysql> SELECT REGEXP_LIKE('CamelCase', 'CAMELCASE');\n+---------------------------------------+\n| REGEXP_LIKE('CamelCase', 'CAMELCASE') |\n+---------------------------------------+\n| 1 |\n+---------------------------------------+\nmysql> SELECT REGEXP_LIKE('CamelCase', 'CAMELCASE' COLLATE utf8mb4_0900_as_cs);\n+------------------------------------------------------------------+\n| REGEXP_LIKE('CamelCase', 'CAMELCASE' COLLATE utf8mb4_0900_as_cs) |\n+------------------------------------------------------------------+\n| 0 |\n ... +[REGEXP_REPLACE] +declaration=expr, pat, repl[, pos[, occurrence[, match_type]]] +category=String Functions +description=Replaces occurrences in the string expr that match the regular\nexpression specified by the pattern pat with the replacement string\nrepl, and returns the resulting string. If expr, pat, or repl is NULL,\nthe return value is NULL.\n\nREGEXP_REPLACE() takes these optional arguments:\n\no pos: The position in expr at which to start the search. If omitted,\n the default is 1.\n\no occurrence: Which occurrence of a match to replace. If omitted, the\n default is 0 (which means "replace all occurrences").\n\no match_type: A string that specifies how to perform matching. The\n meaning is as described for REGEXP_LIKE().\n\nThe result returned by this function uses the character set and\ncollation of the expression searched for matches.\n\nFor additional information about how matching occurs, see the\ndescription for REGEXP_LIKE().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/regexp.html +[REGEXP_SUBSTR] +declaration=expr, pat[, pos[, occurrence[, match_type]]] +category=String Functions +description=Returns the substring of the string expr that matches the regular\nexpression specified by the pattern pat, NULL if there is no match. If\nexpr or pat is NULL, the return value is NULL.\n\nREGEXP_SUBSTR() takes these optional arguments:\n\no pos: The position in expr at which to start the search. If omitted,\n the default is 1.\n\no occurrence: Which occurrence of a match to search for. If omitted,\n the default is 1.\n\no match_type: A string that specifies how to perform matching. The\n meaning is as described for REGEXP_LIKE().\n\nThe result returned by this function uses the character set and\ncollation of the expression searched for matches.\n\nFor additional information about how matching occurs, see the\ndescription for REGEXP_LIKE().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/regexp.html +[RELEASE_ALL_LOCKS] +declaration= +category=Locking Functions +description=Releases all named locks held by the current session and returns the\nnumber of locks released (0 if there were none)\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/locking-functions.html +[RELEASE_LOCK] +declaration=str +category=Locking Functions +description=Releases the lock named by the string str that was obtained with\nGET_LOCK(). Returns 1 if the lock was released, 0 if the lock was not\nestablished by this thread (in which case the lock is not released),\nand NULL if the named lock did not exist. The lock does not exist if it\nwas never obtained by a call to GET_LOCK() or if it has previously been\nreleased.\n\nThe DO statement is convenient to use with RELEASE_LOCK(). See [HELP\nDO].\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/locking-functions.html +[REVERSE] +declaration=str +category=String Functions +description=Returns the string str with the order of the characters reversed, or\nNULL if str is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[RIGHT] +declaration=str,len +category=String Functions +description=Returns the rightmost len characters from the string str, or NULL if\nany argument is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[ROLES_GRAPHML] +declaration= +category=Information Functions +description=Returns a utf8mb3 string containing a GraphML document representing\nmemory role subgraphs. The ROLE_ADMIN privilege (or the deprecated\nSUPER privilege) is required to see content in the element.\nOtherwise, the result shows only an empty element:\n\nmysql> SELECT ROLES_GRAPHML();\n+---------------------------------------------------+\n| ROLES_GRAPHML() |\n+---------------------------------------------------+\n| |\n+---------------------------------------------------+\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[ROUND] +declaration=X +category=Numeric Functions +description=Rounds the argument X to D decimal places. The rounding algorithm\ndepends on the data type of X. D defaults to 0 if not specified. D can\nbe negative to cause D digits left of the decimal point of the value X\nto become zero. The maximum absolute value for D is 30; any digits in\nexcess of 30 (or -30) are truncated. If X or D is NULL, the function\nreturns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[ROW_COUNT] +declaration= +category=Information Functions +description=ROW_COUNT() returns a value as follows:\n\no DDL statements: 0. This applies to statements such as CREATE TABLE or\n DROP TABLE.\n\no DML statements other than SELECT: The number of affected rows. This\n applies to statements such as UPDATE, INSERT, or DELETE (as before),\n but now also to statements such as ALTER TABLE and LOAD DATA.\n\no SELECT: -1 if the statement returns a result set, or the number of\n rows "affected" if it does not. For example, for SELECT * FROM t1,\n ROW_COUNT() returns -1. For SELECT * FROM t1 INTO OUTFILE\n 'file_name', ROW_COUNT() returns the number of rows written to the\n file.\n\no SIGNAL statements: 0.\n\nFor UPDATE statements, the affected-rows value by default is the number\nof rows actually changed. If you specify the CLIENT_FOUND_ROWS flag to\nmysql_real_connect()\n(https://dev.mysql.com/doc/c-api/8.2/en/mysql-real-connect.html) when\nconnecting to mysqld, the affected-rows value is the number of rows\n"found"; that is, matched by the WHERE clause.\n\nFor REPLACE statements, the affected-rows value is 2 if the new row\nreplaced an old row, because in this case, one row was inserted after\nthe duplicate was deleted.\n\nFor INSERT ... ON DUPLICATE KEY UPDATE statements, the affected-rows\nvalue per row is 1 if the row is inserted as a new row, 2 if an\nexisting row is updated, and 0 if an existing row is set to its current\nvalues. If you specify the CLIENT_FOUND_ROWS flag, the affected-rows\nvalue is 1 (not 0) if an existing row is set to its current values.\n\nThe ROW_COUNT() value is similar to the value from the\nmysql_affected_rows()\n(https://dev.mysql.com/doc/c-api/8.2/en/mysql-affected-rows.html) C API\nfunction and the row count that the mysql client displays following\nstatement execution.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[ROW_NUMBER] +declaration= +category=Window Functions +description=Returns the number of the current row within its partition. Rows\nnumbers range from 1 to the number of partition rows.\n\nORDER BY affects the order in which rows are numbered. Without ORDER\nBY, row numbering is nondeterministic.\n\nROW_NUMBER() assigns peers different row numbers. To assign peers the\nsame value, use RANK() or DENSE_RANK(). For an example, see the RANK()\nfunction description.\n\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html +[RPAD] +declaration=str,len,padstr +category=String Functions +description=Returns the string str, right-padded with the string padstr to a length\nof len characters. If str is longer than len, the return value is\nshortened to len characters. If str, padstr, or len is NULL, the\nfunction returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[RTRIM] +declaration=str +category=String Functions +description=Returns the string str with trailing space characters removed.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[SCHEMA] +declaration= +category=Information Functions +description=This function is a synonym for DATABASE().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[SECOND] +declaration=time +category=Date and Time Functions +description=Returns the second for time, in the range 0 to 59, or NULL if time is\nNULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[SEC_TO_TIME] +declaration=seconds +category=Date and Time Functions +description=Returns the seconds argument, converted to hours, minutes, and seconds,\nas a TIME value. The range of the result is constrained to that of the\nTIME data type. A warning occurs if the argument corresponds to a value\noutside that range.\n\nThe function returns NULL if seconds is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[SESSION_USER] +declaration= +category=Information Functions +description=SESSION_USER() is a synonym for USER().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[SHA1] +declaration=str +category=Encryption Functions +description=Calculates an SHA-1 160-bit checksum for the string, as described in\nRFC 3174 (Secure Hash Algorithm). The value is returned as a string of\n40 hexadecimal digits, or NULL if the argument is NULL. One of the\npossible uses for this function is as a hash key. See the notes at the\nbeginning of this section about storing hash values efficiently. SHA()\nis synonymous with SHA1().\n\nThe return value is a string in the connection character set.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[SHA2] +declaration=str, hash_length +category=Encryption Functions +description=Calculates the SHA-2 family of hash functions (SHA-224, SHA-256,\nSHA-384, and SHA-512). The first argument is the plaintext string to be\nhashed. The second argument indicates the desired bit length of the\nresult, which must have a value of 224, 256, 384, 512, or 0 (which is\nequivalent to 256). If either argument is NULL or the hash length is\nnot one of the permitted values, the return value is NULL. Otherwise,\nthe function result is a hash value containing the desired number of\nbits. See the notes at the beginning of this section about storing hash\nvalues efficiently.\n\nThe return value is a string in the connection character set.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[SIGN] +declaration=X +category=Numeric Functions +description=Returns the sign of the argument as -1, 0, or 1, depending on whether X\nis negative, zero, or positive. Returns NULL if X is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[SIN] +declaration=X +category=Numeric Functions +description=Returns the sine of X, where X is given in radians. Returns NULL if X\nis NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[SLEEP] +declaration=duration +category=Miscellaneous Functions +description=Sleeps (pauses) for the number of seconds given by the duration\nargument, then returns 0. The duration may have a fractional part. If\nthe argument is NULL or negative, SLEEP() produces a warning, or an\nerror in strict SQL mode.\n\nWhen sleep returns normally (without interruption), it returns 0:\n\nmysql> SELECT SLEEP(1000);\n+-------------+\n| SLEEP(1000) |\n+-------------+\n| 0 |\n+-------------+\n\nWhen SLEEP() is the only thing invoked by a query that is interrupted,\nit returns 1 and the query itself returns no error. This is true\nwhether the query is killed or times out:\n\no This statement is interrupted using KILL QUERY from another session:\n\nmysql> SELECT SLEEP(1000);\n+-------------+\n| SLEEP(1000) |\n+-------------+\n| 1 |\n+-------------+\n\no This statement is interrupted by timing out:\n\nmysql> SELECT /*+ MAX_EXECUTION_TIME(1) */ SLEEP(1000);\n+-------------+\n| SLEEP(1000) |\n+-------------+\n| 1 |\n+-------------+\n\nWhen SLEEP() is only part of a query that is interrupted, the query\nreturns an error:\n\no This statement is interrupted using KILL QUERY from another session:\n\nmysql> SELECT 1 FROM t1 WHERE SLEEP(1000);\nERROR 1317 (70100): Query execution was interrupted\n\no This statement is interrupted by timing out:\n\nmysql> SELECT /*+ MAX_EXECUTION_TIME(1000) */ 1 FROM t1 WHERE SLEEP(1000);\nERROR 3024 (HY000): Query execution was interrupted, maximum statement\nexecution time exceeded\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[SMALLINT] +declaration=M +category=Data Types +description=A small integer. The signed range is -32768 to 32767. The unsigned\nrange is 0 to 65535.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[SOUNDEX] +declaration=str +category=String Functions +description=Returns a soundex string from str, or NULL if str is NULL. Two strings\nthat sound almost the same should have identical soundex strings. A\nstandard soundex string is four characters long, but the SOUNDEX()\nfunction returns an arbitrarily long string. You can use SUBSTRING() on\nthe result to get a standard soundex string. All nonalphabetic\ncharacters in str are ignored. All international alphabetic characters\noutside the A-Z range are treated as vowels.\n\n*Important*:\n\nWhen using SOUNDEX(), you should be aware of the following limitations:\n\no This function, as currently implemented, is intended to work well\n with strings that are in the English language only. Strings in other\n languages may not produce reliable results.\n\no This function is not guaranteed to provide consistent results with\n strings that use multibyte character sets, including utf-8. See Bug\n #22638 for more information.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[SOURCE_POS_WAIT] +declaration=log_name,log_pos[,timeout][,channel] +category=GTID +description=This function is for control of source-replica synchronization. It\nblocks until the replica has read and applied all updates up to the\nspecified position in the source's binary log.\n\nThe return value is the number of log events the replica had to wait\nfor to advance to the specified position. The function returns NULL if\nthe replication SQL thread is not started, the replica's source\ninformation is not initialized, the arguments are incorrect, or an\nerror occurs. It returns -1 if the timeout has been exceeded. If the\nreplication SQL thread stops while SOURCE_POS_WAIT() is waiting, the\nfunction returns NULL. If the replica is past the specified position,\nthe function returns immediately.\n\nIf the binary log file position has been marked as invalid, the\nfunction waits until a valid file position is known. The binary log\nfile position can be marked as invalid when the CHANGE REPLICATION\nSOURCE TO option GTID_ONLY is set for the replication channel, and the\nserver is restarted or replication is stopped. The file position\nbecomes valid after a transaction is successfully applied past the\ngiven file position. If the applier does not reach the stated position,\nthe function waits until the timeout. Use a SHOW REPLICA STATUS\nstatement to check if the binary log file position has been marked as\ninvalid.\n\nOn a multithreaded replica, the function waits until expiry of the\nlimit set by the replica_checkpoint_group or replica_checkpoint_period\nsystem variable, when the checkpoint operation is called to update the\nstatus of the replica. Depending on the setting for the system\nvariables, the function might therefore return some time after the\nspecified position was reached.\n\nIf binary log transaction compression is in use and the transaction\npayload at the specified position is compressed (as a\nTransaction_payload_event), the function waits until the whole\ntransaction has been read and applied, and the positions have updated.\n\nIf a timeout value is specified, SOURCE_POS_WAIT() stops waiting when\ntimeout seconds have elapsed. timeout must be greater than or equal to\n0. (When the server is running in strict SQL mode, a negative timeout\nvalue is immediately rejected with ER_WRONG_ARGUMENTS\n(https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html\n#error_er_wrong_arguments); otherwise the function returns NULL, and\nraises a warning.)\n\nThe optional channel value enables you to name which replication\nchannel the function applies to. See\nhttps://dev.mysql.com/doc/refman/8.3/en/replication-channels.html for\nmore information.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/replication-functions-synchronization.html +[SPACE] +declaration=N +category=String Functions +description=Returns a string consisting of N space characters, or NULL if N is\nNULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[SQRT] +declaration=X +category=Numeric Functions +description=Returns the square root of a nonnegative number X. If X is NULL, the\nfunction returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[STATEMENT_DIGEST] +declaration=statement +category=Encryption Functions +description=Given an SQL statement as a string, returns the statement digest hash\nvalue as a string in the connection character set, or NULL if the\nargument is NULL. The related STATEMENT_DIGEST_TEXT() function returns\nthe normalized statement digest. For information about statement\ndigesting, see\nhttps://dev.mysql.com/doc/refman/8.3/en/performance-schema-statement-di\ngests.html.\n\nBoth functions use the MySQL parser to parse the statement. If parsing\nfails, an error occurs. The error message includes the parse error only\nif the statement is provided as a literal string.\n\nThe max_digest_length system variable determines the maximum number of\nbytes available to these functions for computing normalized statement\ndigests.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[STATEMENT_DIGEST_TEXT] +declaration=statement +category=Encryption Functions +description=Given an SQL statement as a string, returns the normalized statement\ndigest as a string in the connection character set, or NULL if the\nargument is NULL. For additional discussion and examples, see the\ndescription of the related STATEMENT_DIGEST() function.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[STD] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns the population standard deviation of expr. STD() is a synonym\nfor the standard SQL function STDDEV_POP(), provided as a MySQL\nextension.\n\nIf there are no matching rows, or if expr is NULL, STD() returns NULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[STDDEV] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns the population standard deviation of expr. STDDEV() is a\nsynonym for the standard SQL function STDDEV_POP(), provided for\ncompatibility with Oracle.\n\nIf there are no matching rows, or if expr is NULL, STDDEV() returns\nNULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[STDDEV_POP] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns the population standard deviation of expr (the square root of\nVAR_POP()). You can also use STD() or STDDEV(), which are equivalent\nbut not standard SQL.\n\nIf there are no matching rows, or if expr is NULL, STDDEV_POP() returns\nNULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[STDDEV_SAMP] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns the sample standard deviation of expr (the square root of\nVAR_SAMP().\n\nIf there are no matching rows, or if expr is NULL, STDDEV_SAMP()\nreturns NULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[STRCMP] +declaration=expr1,expr2 +category=String Functions +description=STRCMP() returns 0 if the strings are the same, -1 if the first\nargument is smaller than the second according to the current sort\norder, and NULL if either argument is NULL. It returns 1 otherwise.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-comparison-functions.html +[STR_TO_DATE] +declaration=str,format +category=Date and Time Functions +description=This is the inverse of the DATE_FORMAT() function. It takes a string\nstr and a format string format. STR_TO_DATE() returns a DATETIME value\nif the format string contains both date and time parts, or a DATE or\nTIME value if the string contains only date or time parts. If str or\nformat is NULL, the function returns NULL. If the date, time, or\ndatetime value extracted from str cannot be parsed according to the\nrules followed by the server, STR_TO_DATE() returns NULL and produces a\nwarning.\n\nThe server scans str attempting to match format to it. The format\nstring can contain literal characters and format specifiers beginning\nwith %. Literal characters in format must match literally in str.\nFormat specifiers in format must match a date or time part in str. For\nthe specifiers that can be used in format, see the DATE_FORMAT()\nfunction description.\n\nmysql> SELECT STR_TO_DATE('01,5,2013','%d,%m,%Y');\n -> '2013-05-01'\nmysql> SELECT STR_TO_DATE('May 1, 2013','%M %d,%Y');\n -> '2013-05-01'\n\nScanning starts at the beginning of str and fails if format is found\nnot to match. Extra characters at the end of str are ignored.\n\nmysql> SELECT STR_TO_DATE('a09:30:17','a%h:%i:%s');\n -> '09:30:17'\nmysql> SELECT STR_TO_DATE('a09:30:17','%h:%i:%s');\n -> NULL\nmysql> SELECT STR_TO_DATE('09:30:17a','%h:%i:%s');\n -> '09:30:17'\n\nUnspecified date or time parts have a value of 0, so incompletely\nspecified values in str produce a result with some or all parts set to\n0:\n\nmysql> SELECT STR_TO_DATE('abc','abc');\n -> '0000-00-00'\nmysql> SELECT STR_TO_DATE('9','%m');\n -> '0000-09-00'\nmysql> SELECT STR_TO_DATE('9','%s');\n -> '00:00:09'\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[ST_AREA] +declaration={poly|mpoly} +category=Polygon Property Functions +description=Returns a double-precision number indicating the area of the Polygon or\nMultiPolygon argument, as measured in its spatial reference system.\n\nST_Area() handles its arguments as described in the introduction to\nthis section, with these exceptions:\n\no If the geometry is geometrically invalid, either the result is an\n undefined area (that is, it can be any number), or an error occurs.\n\no If the geometry is valid but is not a Polygon or MultiPolygon object,\n an ER_UNEXPECTED_GEOMETRY_TYPE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_unexpected_geometry_type) error occurs.\n\no If the geometry is a valid Polygon in a Cartesian SRS, the result is\n the Cartesian area of the polygon.\n\no If the geometry is a valid MultiPolygon in a Cartesian SRS, the\n result is the sum of the Cartesian area of the polygons.\n\no If the geometry is a valid Polygon in a geographic SRS, the result is\n the geodetic area of the polygon in that SRS, in square meters.\n\no If the geometry is a valid MultiPolygon in a geographic SRS, the\n result is the sum of geodetic area of the polygons in that SRS, in\n square meters.\n\no If an area computation results in +inf, an ER_DATA_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_data_out_of_range) error occurs.\n\no If the geometry has a geographic SRS with a longitude or latitude\n that is out of range, an error occurs:\n\n o If a longitude value is not in the range (−180, 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\n o If a latitude value is not in the range [−90, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_latitude_out_of_range) error\n occurs.\n\n Ranges shown are in degrees. The exact range limits deviate slightly\n due to floating-point arithmetic.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-polygon-property-functions.html +[ST_ASBINARY] +declaration=g [, options] +category=WKB Functions +description=Converts a value in internal geometry format to its WKB representation\nand returns the binary result.\n\nThe function return value has geographic coordinates (latitude,\nlongitude) in the order specified by the spatial reference system that\napplies to the geometry argument. An optional options argument may be\ngiven to override the default axis order.\n\nST_AsBinary() and ST_AsWKB() handle their arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-format-conversion-functions.html +[ST_ASGEOJSON] +declaration=g [, max_dec_digits [, options]] +category=MBR Functions +description=Generates a GeoJSON object from the geometry g. The object string has\nthe connection character set and collation.\n\nIf any argument is NULL, the return value is NULL. If any non-NULL\nargument is invalid, an error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geojson-functions.html +[ST_ASTEXT] +declaration=g [, options] +category=WKB Functions +description=Converts a value in internal geometry format to its WKT representation\nand returns the string result.\n\nThe function return value has geographic coordinates (latitude,\nlongitude) in the order specified by the spatial reference system that\napplies to the geometry argument. An optional options argument may be\ngiven to override the default axis order.\n\nST_AsText() and ST_AsWKT() handle their arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-format-conversion-functions.html +[ST_BUFFER] +declaration=g, d [, strategy1 [, strategy2 [, strategy3]]] +category=GeometryCollection Property Functions +description=Returns a geometry that represents all points whose distance from the\ngeometry value g is less than or equal to a distance of d. The result\nis in the same SRS as the geometry argument.\n\nIf the geometry argument is empty, ST_Buffer() returns an empty\ngeometry.\n\nIf the distance is 0, ST_Buffer() returns the geometry argument\nunchanged:\n\nmysql> SET @pt = ST_GeomFromText('POINT(0 0)');\nmysql> SELECT ST_AsText(ST_Buffer(@pt, 0));\n+------------------------------+\n| ST_AsText(ST_Buffer(@pt, 0)) |\n+------------------------------+\n| POINT(0 0) |\n+------------------------------+\n\nIf the geometry argument is in a Cartesian SRS:\n\no ST_Buffer() supports negative distances for Polygon and MultiPolygon\n values, and for geometry collections containing Polygon or\n MultiPolygon values.\n\no If the result is reduced so much that it disappears, the result is an\n empty geometry.\n\no An ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_wrong_arguments) error occurs for ST_Buffer() with a\n negative distance for Point, MultiPoint, LineString, and\n MultiLineString values, and for geometry collections not containing\n any Polygon or MultiPolygon values.\n\nPoint geometries in a geographic SRS are permitted, subject to the\nfollowing conditions:\n\no If the distance is not negative and no strategies are specified, the\n function returns the geographic buffer of the Point in its SRS. The\n distance argument must be in the SRS distance unit (currently always\n meters).\n\no If the distance is negative or any strategy (except NULL) is\n specified, an ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_wrong_arguments) error occurs.\n\nFor non-Point geometries, an ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n(https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html\n#error_er_not_implemented_for_geographic_srs) error occurs.\n ... +[ST_BUFFER_STRATEGY] +declaration=strategy [, points_per_circle] +category=GeometryCollection Property Functions +description=This function returns a strategy byte string for use with ST_Buffer()\nto influence buffer computation.\n\nInformation about strategies is available at Boost.org\n(http://www.boost.org).\n\nThe first argument must be a string indicating a strategy option:\n\no For point strategies, permitted values are 'point_circle' and\n 'point_square'.\n\no For join strategies, permitted values are 'join_round' and\n 'join_miter'.\n\no For end strategies, permitted values are 'end_round' and 'end_flat'.\n\nIf the first argument is 'point_circle', 'join_round', 'join_miter', or\n'end_round', the points_per_circle argument must be given as a positive\nnumeric value. The maximum points_per_circle value is the value of the\nmax_points_in_geometry system variable.\n\nFor examples, see the description of ST_Buffer().\n\nST_Buffer_Strategy() handles its arguments as described in the\nintroduction to this section, with these exceptions:\n\no If any argument is invalid, an ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_wrong_arguments) error occurs.\n\no If the first argument is 'point_square' or 'end_flat', the\n points_per_circle argument must not be given or an ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_wrong_arguments) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html +[ST_CENTROID] +declaration={poly|mpoly} +category=Polygon Property Functions +description=Returns the mathematical centroid for the Polygon or MultiPolygon\nargument as a Point. The result is not guaranteed to be on the\nMultiPolygon.\n\nThis function processes geometry collections by computing the centroid\npoint for components of highest dimension in the collection. Such\ncomponents are extracted and made into a single MultiPolygon,\nMultiLineString, or MultiPoint for centroid computation.\n\nST_Centroid() handles its arguments as described in the introduction to\nthis section, with these exceptions:\n\no The return value is NULL for the additional condition that the\n argument is an empty geometry collection.\n\no If the geometry has an SRID value for a geographic spatial reference\n system (SRS), an ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_geographic_srs) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-polygon-property-functions.html +[ST_COLLECT] +declaration=[DISTINCT] g +category=MBR Functions +description=Aggregates geometry values and returns a single geometry collection\nvalue. With the DISTINCT option, returns the aggregation of the\ndistinct geometry arguments.\n\nAs with other aggregate functions, GROUP BY may be used to group\narguments into subsets. ST_Collect() returns an aggregate value for\neach subset.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. In\ncontrast to most aggregate functions that support windowing,\nST_Collect() permits use of over_clause together with DISTINCT.\n\nST_Collect() handles its arguments as follows:\n\no NULL arguments are ignored.\n\no If all arguments are NULL or the aggregate result is empty, the\n return value is NULL.\n\no If any geometry argument is not a syntactically well-formed geometry,\n an ER_GIS_INVALID_DATA\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_gis_invalid_data) error occurs.\n\no If any geometry argument is a syntactically well-formed geometry in\n an undefined spatial reference system (SRS), an ER_SRS_NOT_FOUND\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_srs_not_found) error occurs.\n\no If there are multiple geometry arguments and those arguments are in\n the same SRS, the return value is in that SRS. If those arguments are\n not in the same SRS, an ER_GIS_DIFFERENT_SRIDS_AGGREGATION\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_gis_different_srids_aggregation) error occurs.\n\no The result is the narrowest MultiXxx or GeometryCollection value\n possible, with the result type determined from the non-NULL geometry\n arguments as follows:\n\n o If all arguments are Point values, the result is a MultiPoint\n value.\n\n o If all arguments are LineString values, the result is a\n MultiLineString value.\n\n o If all arguments are Polygon values, the result is a MultiPolygon\n value.\n\n ... +[ST_CONTAINS] +declaration=g1, g2 +category=Geometry Relation Functions +description=Returns 1 or 0 to indicate whether g1 completely contains g2. This\ntests the opposite relationship as ST_Within().\n\nST_Contains() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_CONVEXHULL] +declaration=g +category=GeometryCollection Property Functions +description=Returns a geometry that represents the convex hull of the geometry\nvalue g.\n\nThis function computes a geometry's convex hull by first checking\nwhether its vertex points are colinear. The function returns a linear\nhull if so, a polygon hull otherwise. This function processes geometry\ncollections by extracting all vertex points of all components of the\ncollection, creating a MultiPoint value from them, and computing its\nconvex hull.\n\nST_ConvexHull() handles its arguments as described in the introduction\nto this section, with this exception:\n\no The return value is NULL for the additional condition that the\n argument is an empty geometry collection.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html +[ST_CROSSES] +declaration=g1, g2 +category=Geometry Relation Functions +description=Two geometries spatially cross if their spatial relation has the\nfollowing properties:\n\no Unless g1 and g2 are both of dimension 1: g1 crosses g2 if the\n interior of g2 has points in common with the interior of g1, but g2\n does not cover the entire interior of g1.\n\no If both g1 and g2 are of dimension 1: If the lines cross each other\n in a finite number of points (that is, no common line segments, only\n single points in common).\n\nThis function returns 1 or 0 to indicate whether g1 spatially crosses\ng2.\n\nST_Crosses() handles its arguments as described in the introduction to\nthis section except that the return value is NULL for these additional\nconditions:\n\no g1 is of dimension 2 (Polygon or MultiPolygon).\n\no g2 is of dimension 1 (Point or MultiPoint).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_DIFFERENCE] +declaration=g1, g2 +category=GeometryCollection Property Functions +description=Returns a geometry that represents the point set difference of the\ngeometry values g1 and g2. The result is in the same SRS as the\ngeometry arguments.\n\nST_Difference() permits arguments in either a Cartesian or a geographic\nSRS, and handles its arguments as described in the introduction to this\nsection.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html +[ST_DIMENSION] +declaration=g +category=Geometry Property Functions +description=Returns the inherent dimension of the geometry value g. The dimension\ncan be −1, 0, 1, or 2. The meaning of these values is given in\nhttps://dev.mysql.com/doc/refman/8.3/en/gis-class-geometry.html.\n\nST_Dimension() handles its arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html +[ST_DISJOINT] +declaration=g1, g2 +category=Geometry Relation Functions +description=Returns 1 or 0 to indicate whether g1 is spatially disjoint from (does\nnot intersect) g2.\n\nST_Disjoint() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_DISTANCE] +declaration=g1, g2 [, unit] +category=Geometry Relation Functions +description=Returns the distance between g1 and g2, measured in the length unit of\nthe spatial reference system (SRS) of the geometry arguments, or in the\nunit of the optional unit argument if that is specified.\n\nThis function processes geometry collections by returning the shortest\ndistance among all combinations of the components of the two geometry\narguments.\n\nST_Distance() handles its geometry arguments as described in the\nintroduction to this section, with these exceptions:\n\no ST_Distance() detects arguments in a geographic (ellipsoidal) spatial\n reference system and returns the geodetic distance on the ellipsoid.\n ST_Distance() supports distance calculations for geographic SRS\n arguments of all geometry types.\n\no If any argument is geometrically invalid, either the result is an\n undefined distance (that is, it can be any number), or an error\n occurs.\n\no If an intermediate or final result produces NaN or a negative number,\n an ER_GIS_INVALID_DATA\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_gis_invalid_data) error occurs.\n\nST_Distance() permits specifying the linear unit for the returned\ndistance value with an optional unit argument which ST_Distance()\nhandles as described in the introduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_DISTANCE_SPHERE] +declaration=g1, g2 [, radius] +category=MBR Functions +description=Returns the minimum spherical distance between Point or MultiPoint\narguments on a sphere, in meters. (For general-purpose distance\ncalculations, see the ST_Distance() function.) The optional radius\nargument should be given in meters.\n\nIf both geometry parameters are valid Cartesian Point or MultiPoint\nvalues in SRID 0, the return value is shortest distance between the two\ngeometries on a sphere with the provided radius. If omitted, the\ndefault radius is 6,370,986 meters, Point X and Y coordinates are\ninterpreted as longitude and latitude, respectively, in degrees.\n\nIf both geometry parameters are valid Point or MultiPoint values in a\ngeographic spatial reference system (SRS), the return value is the\nshortest distance between the two geometries on a sphere with the\nprovided radius. If omitted, the default radius is equal to the mean\nradius, defined as (2a+b)/3, where a is the semi-major axis and b is\nthe semi-minor axis of the SRS.\n\nST_Distance_Sphere() handles its arguments as described in the\nintroduction to this section, with these exceptions:\n\no Supported geometry argument combinations are Point and Point, or\n Point and MultiPoint (in any argument order). If at least one of the\n geometries is neither Point nor MultiPoint, and its SRID is 0, an\n ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_cartesian_srs) error occurs. If at\n least one of the geometries is neither Point nor MultiPoint, and its\n SRID refers to a geographic SRS, an\n ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_geographic_srs) error occurs. If\n any geometry refers to a projected SRS, an\n ER_NOT_IMPLEMENTED_FOR_PROJECTED_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_projected_srs) error occurs.\n\no If any argument has a longitude or latitude that is out of range, an\n error occurs:\n\n o If a longitude value is not in the range (−180, 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\n o If a latitude value is not in the range [−90, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_latitude_out_of_range) error\n ... +[ST_ENDPOINT] +declaration=ls +category=LineString Property Functions +description=Returns the Point that is the endpoint of the LineString value ls.\n\nST_EndPoint() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-linestring-property-functions.html +[ST_ENVELOPE] +declaration=g +category=Geometry Property Functions +description=Returns the minimum bounding rectangle (MBR) for the geometry value g.\nThe result is returned as a Polygon value that is defined by the corner\npoints of the bounding box:\n\nPOLYGON((MINX MINY, MAXX MINY, MAXX MAXY, MINX MAXY, MINX MINY))\n\nmysql> SELECT ST_AsText(ST_Envelope(ST_GeomFromText('LineString(1 1,2 2)')));\n+----------------------------------------------------------------+\n| ST_AsText(ST_Envelope(ST_GeomFromText('LineString(1 1,2 2)'))) |\n+----------------------------------------------------------------+\n| POLYGON((1 1,2 1,2 2,1 2,1 1)) |\n+----------------------------------------------------------------+\n\nIf the argument is a point or a vertical or horizontal line segment,\nST_Envelope() returns the point or the line segment as its MBR rather\nthan returning an invalid polygon:\n\nmysql> SELECT ST_AsText(ST_Envelope(ST_GeomFromText('LineString(1 1,1 2)')));\n+----------------------------------------------------------------+\n| ST_AsText(ST_Envelope(ST_GeomFromText('LineString(1 1,1 2)'))) |\n+----------------------------------------------------------------+\n| LINESTRING(1 1,1 2) |\n+----------------------------------------------------------------+\n\nST_Envelope() handles its arguments as described in the introduction to\nthis section, with this exception:\n\no If the geometry has an SRID value for a geographic spatial reference\n system (SRS), an ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_geographic_srs) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html +[ST_EQUALS] +declaration=g1, g2 +category=Geometry Relation Functions +description=Returns 1 or 0 to indicate whether g1 is spatially equal to g2.\n\nST_Equals() handles its arguments as described in the introduction to\nthis section, except that it does not return NULL for empty geometry\narguments.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_EXTERIORRING] +declaration=poly +category=Polygon Property Functions +description=Returns the exterior ring of the Polygon value poly as a LineString.\n\nST_ExteriorRing() handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-polygon-property-functions.html +[ST_FRECHETDISTANCE] +declaration=g1, g2 [, unit] +category=Geometry Relation Functions +description=Returns the discrete Fréchet distance between two geometries,\nreflecting how similar the geometries are. The result is a\ndouble-precision number measured in the length unit of the spatial\nreference system (SRS) of the geometry arguments, or in the length unit\nof the unit argument if that argument is given.\n\nThis function implements the discrete Fréchet distance, which means it\nis restricted to distances between the points of the geometries. For\nexample, given two LineString arguments, only the points explicitly\nmentioned in the geometries are considered. Points on the line segments\nbetween these points are not considered.\n\nST_FrechetDistance() handles its geometry arguments as described in the\nintroduction to this section, with these exceptions:\n\no The geometries may have a Cartesian or geographic SRS, but only\n LineString values are supported. If the arguments are in the same\n Cartesian or geographic SRS, but either is not a LineString, an\n ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_cartesian_srs) or\n ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_geographic_srs) error occurs,\n depending on the SRS type.\n\nST_FrechetDistance() handles its optional unit argument as described in\nthe introduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_GEOHASH] +declaration=longitude, latitude, max_length +category=MBR Functions +description=max_length)\n\nReturns a geohash string in the connection character set and collation.\n\nFor the first syntax, the longitude must be a number in the range\n[−180, 180], and the latitude must be a number in the range [−90,\n90]. For the second syntax, a POINT value is required, where the X and\nY coordinates are in the valid ranges for longitude and latitude,\nrespectively.\n\nThe resulting string is no longer than max_length characters, which has\nan upper limit of 100. The string might be shorter than max_length\ncharacters because the algorithm that creates the geohash value\ncontinues until it has created a string that is either an exact\nrepresentation of the location or max_length characters, whichever\ncomes first.\n\nST_GeoHash() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geohash-functions.html +[ST_GEOMCOLLFROMTEXT] +declaration=wkt [, srid [, options]] +category=WKT Functions +description=ST_GeometryCollectionFromText(wkt [, srid [, options]]),\nST_GeomCollFromTxt(wkt [, srid [, options]])\n\nConstructs a GeometryCollection value using its WKT representation and\nSRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html +[ST_GEOMCOLLFROMWKB] +declaration=wkb [, srid [, options]] +category=WKB Functions +description=ST_GeometryCollectionFromWKB(wkb [, srid [, options]])\n\nConstructs a GeometryCollection value using its WKB representation and\nSRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html +[ST_GEOMETRYN] +declaration=gc, N +category=GeometryCollection Property Functions +description=Returns the N-th geometry in the GeometryCollection value gc.\nGeometries are numbered beginning with 1.\n\nST_GeometryN() handles its arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-geometrycollection-property-functions.html +[ST_GEOMETRYTYPE] +declaration=g +category=Geometry Property Functions +description=Returns a binary string indicating the name of the geometry type of\nwhich the geometry instance g is a member. The name corresponds to one\nof the instantiable Geometry subclasses.\n\nST_GeometryType() handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html +[ST_GEOMFROMGEOJSON] +declaration=str [, options [, srid]] +category=MBR Functions +description=Parses a string str representing a GeoJSON object and returns a\ngeometry.\n\nIf any argument is NULL, the return value is NULL. If any non-NULL\nargument is invalid, an error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geojson-functions.html +[ST_GEOMFROMTEXT] +declaration=wkt [, srid [, options]] +category=WKT Functions +description=srid [, options]])\n\nConstructs a geometry value of any type using its WKT representation\nand SRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html +[ST_GEOMFROMWKB] +declaration=wkb [, srid [, options]] +category=WKB Functions +description=srid [, options]])\n\nConstructs a geometry value of any type using its WKB representation\nand SRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html +[ST_HAUSDORFFDISTANCE] +declaration=g1, g2 [, unit] +category=Geometry Relation Functions +description=Returns the discrete Hausdorff distance between two geometries,\nreflecting how similar the geometries are. The result is a\ndouble-precision number measured in the length unit of the spatial\nreference system (SRS) of the geometry arguments, or in the length unit\nof the unit argument if that argument is given.\n\nThis function implements the discrete Hausdorff distance, which means\nit is restricted to distances between the points of the geometries. For\nexample, given two LineString arguments, only the points explicitly\nmentioned in the geometries are considered. Points on the line segments\nbetween these points are not considered.\n\nST_HausdorffDistance() handles its geometry arguments as described in\nthe introduction to this section, with these exceptions:\n\no If the geometry arguments are in the same Cartesian or geographic\n SRS, but are not in a supported combination, an\n ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_cartesian_srs) or\n ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_geographic_srs) error occurs,\n depending on the SRS type. These combinations are supported:\n\n o LineString and LineString\n\n o Point and MultiPoint\n\n o LineString and MultiLineString\n\n o MultiPoint and MultiPoint\n\n o MultiLineString and MultiLineString\n\nST_HausdorffDistance() handles its optional unit argument as described\nin the introduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_INTERIORRINGN] +declaration=poly, N +category=Polygon Property Functions +description=Returns the N-th interior ring for the Polygon value poly as a\nLineString. Rings are numbered beginning with 1.\n\nST_InteriorRingN() handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-polygon-property-functions.html +[ST_INTERSECTION] +declaration=g1, g2 +category=GeometryCollection Property Functions +description=Returns a geometry that represents the point set intersection of the\ngeometry values g1 and g2. The result is in the same SRS as the\ngeometry arguments.\n\nST_Intersection() permits arguments in either a Cartesian or a\ngeographic SRS, and handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html +[ST_INTERSECTS] +declaration=g1, g2 +category=Geometry Relation Functions +description=Returns 1 or 0 to indicate whether g1 spatially intersects g2.\n\nST_Intersects() handles its arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_ISCLOSED] +declaration=ls +category=LineString Property Functions +description=For a LineString value ls, ST_IsClosed() returns 1 if ls is closed\n(that is, its ST_StartPoint() and ST_EndPoint() values are the same).\n\nFor a MultiLineString value ls, ST_IsClosed() returns 1 if ls is closed\n(that is, the ST_StartPoint() and ST_EndPoint() values are the same for\neach LineString in ls).\n\nST_IsClosed() returns 0 if ls is not closed, and NULL if ls is NULL.\n\nST_IsClosed() handles its arguments as described in the introduction to\nthis section, with this exception:\n\no If the geometry has an SRID value for a geographic spatial reference\n system (SRS), an ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_geographic_srs) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-linestring-property-functions.html +[ST_ISEMPTY] +declaration=g +category=Geometry Property Functions +description=This function is a placeholder that returns 1 for an empty geometry\ncollection value or 0 otherwise.\n\nThe only valid empty geometry is represented in the form of an empty\ngeometry collection value. MySQL does not support GIS EMPTY values such\nas POINT EMPTY.\n\nST_IsEmpty() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html +[ST_ISSIMPLE] +declaration=g +category=Geometry Property Functions +description=Returns 1 if the geometry value g is simple according to the ISO SQL/MM\nPart 3: Spatial standard. ST_IsSimple() returns 0 if the argument is\nnot simple.\n\nThe descriptions of the instantiable geometric classes given under\nhttps://dev.mysql.com/doc/refman/8.3/en/opengis-geometry-model.html\ninclude the specific conditions that cause class instances to be\nclassified as not simple.\n\nST_IsSimple() handles its arguments as described in the introduction to\nthis section, with this exception:\n\no If the geometry has a geographic SRS with a longitude or latitude\n that is out of range, an error occurs:\n\n o If a longitude value is not in the range (−180, 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\n o If a latitude value is not in the range [−90, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_latitude_out_of_range) error\n occurs.\n\n Ranges shown are in degrees. The exact range limits deviate slightly\n due to floating-point arithmetic.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html +[ST_ISVALID] +declaration=g +category=MBR Functions +description=Returns 1 if the argument is geometrically valid, 0 if the argument is\nnot geometrically valid. Geometry validity is defined by the OGC\nspecification.\n\nThe only valid empty geometry is represented in the form of an empty\ngeometry collection value. ST_IsValid() returns 1 in this case. MySQL\ndoes not support GIS EMPTY values such as POINT EMPTY.\n\nST_IsValid() handles its arguments as described in the introduction to\nthis section, with this exception:\n\no If the geometry has a geographic SRS with a longitude or latitude\n that is out of range, an error occurs:\n\n o If a longitude value is not in the range (−180, 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\n o If a latitude value is not in the range [−90, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_latitude_out_of_range) error\n occurs.\n\n Ranges shown are in degrees. If an SRS uses another unit, the range\n uses the corresponding values in its unit. The exact range limits\n deviate slightly due to floating-point arithmetic.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-convenience-functions.html +[ST_LATFROMGEOHASH] +declaration=geohash_str +category=MBR Functions +description=Returns the latitude from a geohash string value, as a double-precision\nnumber in the range [−90, 90].\n\nThe ST_LatFromGeoHash() decoding function reads no more than 433\ncharacters from the geohash_str argument. That represents the upper\nlimit on information in the internal representation of coordinate\nvalues. Characters past the 433rd are ignored, even if they are\notherwise illegal and produce an error.\n\nST_LatFromGeoHash() handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geohash-functions.html +[ST_LATITUDE] +declaration=p [, new_latitude_val] +category=Point Property Functions +description=With a single argument representing a valid Point object p that has a\ngeographic spatial reference system (SRS), ST_Latitude() returns the\nlatitude value of p as a double-precision number.\n\nWith the optional second argument representing a valid latitude value,\nST_Latitude() returns a Point object like the first argument with its\nlatitude equal to the second argument.\n\nST_Latitude() handles its arguments as described in the introduction to\nthis section, with the addition that if the Point object is valid but\ndoes not have a geographic SRS, an ER_SRS_NOT_GEOGRAPHIC\n(https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html\n#error_er_srs_not_geographic) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-point-property-functions.html +[ST_LENGTH] +declaration=ls [, unit] +category=LineString Property Functions +description=Returns a double-precision number indicating the length of the\nLineString or MultiLineString value ls in its associated spatial\nreference system. The length of a MultiLineString value is equal to the\nsum of the lengths of its elements.\n\nST_Length() computes a result as follows:\n\no If the geometry is a valid LineString in a Cartesian SRS, the return\n value is the Cartesian length of the geometry.\n\no If the geometry is a valid MultiLineString in a Cartesian SRS, the\n return value is the sum of the Cartesian lengths of its elements.\n\no If the geometry is a valid LineString in a geographic SRS, the return\n value is the geodetic length of the geometry in that SRS, in meters.\n\no If the geometry is a valid MultiLineString in a geographic SRS, the\n return value is the sum of the geodetic lengths of its elements in\n that SRS, in meters.\n\nST_Length() handles its arguments as described in the introduction to\nthis section, with these exceptions:\n\no If the geometry is not a LineString or MultiLineString, the return\n value is NULL.\n\no If the geometry is geometrically invalid, either the result is an\n undefined length (that is, it can be any number), or an error occurs.\n\no If the length computation result is +inf, an ER_DATA_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_data_out_of_range) error occurs.\n\no If the geometry has a geographic SRS with a longitude or latitude\n that is out of range, an error occurs:\n\n o If a longitude value is not in the range (−180, 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\n o If a latitude value is not in the range [−90, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_latitude_out_of_range) error\n occurs.\n\n Ranges shown are in degrees. The exact range limits deviate slightly\n due to floating-point arithmetic.\n ... +[ST_LINEFROMTEXT] +declaration=wkt [, srid [, options]] +category=WKT Functions +description=srid [, options]])\n\nConstructs a LineString value using its WKT representation and SRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html +[ST_LINEFROMWKB] +declaration=wkb [, srid [, options]] +category=WKB Functions +description=srid [, options]])\n\nConstructs a LineString value using its WKB representation and SRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html +[ST_LINEINTERPOLATEPOINT] +declaration=ls, fractional_distance +category=GeometryCollection Property Functions +description=This function takes a LineString geometry and a fractional distance in\nthe range [0.0, 1.0] and returns the Point along the LineString at the\ngiven fraction of the distance from its start point to its endpoint. It\ncan be used to answer questions such as which Point lies halfway along\nthe road described by the geometry argument.\n\nThe function is implemented for LineString geometries in all spatial\nreference systems, both Cartesian and geographic.\n\nIf the fractional_distance argument is 1.0, the result may not be\nexactly the last point of the LineString argument but a point close to\nit due to numerical inaccuracies in approximate-value computations.\n\nA related function, ST_LineInterpolatePoints(), takes similar arguments\nbut returns a MultiPoint consisting of Point values along the\nLineString at each fraction of the distance from its start point to its\nendpoint. For examples of both functions, see the\nST_LineInterpolatePoints() description.\n\nST_LineInterpolatePoint() handles its arguments as described in the\nintroduction to this section, with these exceptions:\n\no If the geometry argument is not a LineString, an\n ER_UNEXPECTED_GEOMETRY_TYPE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_unexpected_geometry_type) error occurs.\n\no If the fractional distance argument is outside the range [0.0, 1.0],\n an ER_DATA_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_data_out_of_range) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html +[ST_LINEINTERPOLATEPOINTS] +declaration=ls, fractional_distance +category=GeometryCollection Property Functions +description=This function takes a LineString geometry and a fractional distance in\nthe range (0.0, 1.0] and returns the MultiPoint consisting of the\nLineString start point, plus Point values along the LineString at each\nfraction of the distance from its start point to its endpoint. It can\nbe used to answer questions such as which Point values lie every 10% of\nthe way along the road described by the geometry argument.\n\nThe function is implemented for LineString geometries in all spatial\nreference systems, both Cartesian and geographic.\n\nIf the fractional_distance argument divides 1.0 with zero remainder the\nresult may not contain the last point of the LineString argument but a\npoint close to it due to numerical inaccuracies in approximate-value\ncomputations.\n\nA related function, ST_LineInterpolatePoint(), takes similar arguments\nbut returns the Point along the LineString at the given fraction of the\ndistance from its start point to its endpoint.\n\nST_LineInterpolatePoints() handles its arguments as described in the\nintroduction to this section, with these exceptions:\n\no If the geometry argument is not a LineString, an\n ER_UNEXPECTED_GEOMETRY_TYPE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_unexpected_geometry_type) error occurs.\n\no If the fractional distance argument is outside the range [0.0, 1.0],\n an ER_DATA_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_data_out_of_range) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html +[ST_LONGFROMGEOHASH] +declaration=geohash_str +category=MBR Functions +description=Returns the longitude from a geohash string value, as a\ndouble-precision number in the range [−180, 180].\n\nThe remarks in the description of ST_LatFromGeoHash() regarding the\nmaximum number of characters processed from the geohash_str argument\nalso apply to ST_LongFromGeoHash().\n\nST_LongFromGeoHash() handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geohash-functions.html +[ST_LONGITUDE] +declaration=p [, new_longitude_val] +category=Point Property Functions +description=With a single argument representing a valid Point object p that has a\ngeographic spatial reference system (SRS), ST_Longitude() returns the\nlongitude value of p as a double-precision number.\n\nWith the optional second argument representing a valid longitude value,\nST_Longitude() returns a Point object like the first argument with its\nlongitude equal to the second argument.\n\nST_Longitude() handles its arguments as described in the introduction\nto this section, with the addition that if the Point object is valid\nbut does not have a geographic SRS, an ER_SRS_NOT_GEOGRAPHIC\n(https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html\n#error_er_srs_not_geographic) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-point-property-functions.html +[ST_MAKEENVELOPE] +declaration=pt1, pt2 +category=MBR Functions +description=Returns the rectangle that forms the envelope around two points, as a\nPoint, LineString, or Polygon.\n\nCalculations are done using the Cartesian coordinate system rather than\non a sphere, spheroid, or on earth.\n\nGiven two points pt1 and pt2, ST_MakeEnvelope() creates the result\ngeometry on an abstract plane like this:\n\no If pt1 and pt2 are equal, the result is the point pt1.\n\no Otherwise, if (pt1, pt2) is a vertical or horizontal line segment,\n the result is the line segment (pt1, pt2).\n\no Otherwise, the result is a polygon using pt1 and pt2 as diagonal\n points.\n\nThe result geometry has an SRID of 0.\n\nST_MakeEnvelope() handles its arguments as described in the\nintroduction to this section, with these exceptions:\n\no If the arguments are not Point values, an ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_wrong_arguments) error occurs.\n\no An ER_GIS_INVALID_DATA\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_gis_invalid_data) error occurs for the additional\n condition that any coordinate value of the two points is infinite or\n NaN.\n\no If any geometry has an SRID value for a geographic spatial reference\n system (SRS), an ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_not_implemented_for_geographic_srs) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-convenience-functions.html +[ST_MLINEFROMTEXT] +declaration=wkt [, srid [, options]] +category=WKT Functions +description=ST_MultiLineStringFromText(wkt [, srid [, options]])\n\nConstructs a MultiLineString value using its WKT representation and\nSRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html +[ST_MLINEFROMWKB] +declaration=wkb [, srid [, options]] +category=WKB Functions +description=ST_MultiLineStringFromWKB(wkb [, srid [, options]])\n\nConstructs a MultiLineString value using its WKB representation and\nSRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html +[ST_MPOINTFROMTEXT] +declaration=wkt [, srid [, options]] +category=WKT Functions +description=[, srid [, options]])\n\nConstructs a MultiPoint value using its WKT representation and SRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html +[ST_MPOINTFROMWKB] +declaration=wkb [, srid [, options]] +category=WKB Functions +description=srid [, options]])\n\nConstructs a MultiPoint value using its WKB representation and SRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html +[ST_MPOLYFROMTEXT] +declaration=wkt [, srid [, options]] +category=WKT Functions +description=[, srid [, options]])\n\nConstructs a MultiPolygon value using its WKT representation and SRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html +[ST_MPOLYFROMWKB] +declaration=wkb [, srid [, options]] +category=WKB Functions +description=[, srid [, options]])\n\nConstructs a MultiPolygon value using its WKB representation and SRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html +[ST_NUMGEOMETRIES] +declaration=gc +category=GeometryCollection Property Functions +description=Returns the number of geometries in the GeometryCollection value gc.\n\nST_NumGeometries() handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-geometrycollection-property-functions.html +[ST_NUMINTERIORRINGS] +declaration=poly +category=Polygon Property Functions +description=Returns the number of interior rings in the Polygon value poly.\n\nST_NumInteriorRing() and ST_NuminteriorRings() handle their arguments\nas described in the introduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-polygon-property-functions.html +[ST_NUMPOINTS] +declaration=ls +category=LineString Property Functions +description=Returns the number of Point objects in the LineString value ls.\n\nST_NumPoints() handles its arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-linestring-property-functions.html +[ST_OVERLAPS] +declaration=g1, g2 +category=Geometry Relation Functions +description=Two geometries spatially overlap if they intersect and their\nintersection results in a geometry of the same dimension but not equal\nto either of the given geometries.\n\nThis function returns 1 or 0 to indicate whether g1 spatially overlaps\ng2.\n\nST_Overlaps() handles its arguments as described in the introduction to\nthis section except that the return value is NULL for the additional\ncondition that the dimensions of the two geometries are not equal.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_POINTATDISTANCE] +declaration=ls, distance +category=GeometryCollection Property Functions +description=This function takes a LineString geometry and a distance in the range\n[0.0, ST_Length(ls)] measured in the unit of the spatial reference\nsystem (SRS) of the LineString, and returns the Point along the\nLineString at that distance from its start point. It can be used to\nanswer questions such as which Point value is 400 meters from the start\nof the road described by the geometry argument.\n\nThe function is implemented for LineString geometries in all spatial\nreference systems, both Cartesian and geographic.\n\nST_PointAtDistance() handles its arguments as described in the\nintroduction to this section, with these exceptions:\n\no If the geometry argument is not a LineString, an\n ER_UNEXPECTED_GEOMETRY_TYPE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_unexpected_geometry_type) error occurs.\n\no If the fractional distance argument is outside the range [0.0,\n ST_Length(ls)], an ER_DATA_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_data_out_of_range) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html +[ST_POINTFROMGEOHASH] +declaration=geohash_str, srid +category=MBR Functions +description=Returns a POINT value containing the decoded geohash value, given a\ngeohash string value.\n\nThe X and Y coordinates of the point are the longitude in the range\n[−180, 180] and the latitude in the range [−90, 90], respectively.\n\nThe srid argument is an 32-bit unsigned integer.\n\nThe remarks in the description of ST_LatFromGeoHash() regarding the\nmaximum number of characters processed from the geohash_str argument\nalso apply to ST_PointFromGeoHash().\n\nST_PointFromGeoHash() handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geohash-functions.html +[ST_POINTFROMTEXT] +declaration=wkt [, srid [, options]] +category=WKT Functions +description=Constructs a Point value using its WKT representation and SRID.\n\nST_PointFromText() handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html +[ST_POINTFROMWKB] +declaration=wkb [, srid [, options]] +category=WKB Functions +description=Constructs a Point value using its WKB representation and SRID.\n\nST_PointFromWKB() handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html +[ST_POINTN] +declaration=ls, N +category=LineString Property Functions +description=Returns the N-th Point in the Linestring value ls. Points are numbered\nbeginning with 1.\n\nST_PointN() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-linestring-property-functions.html +[ST_POLYFROMTEXT] +declaration=wkt [, srid [, options]] +category=WKT Functions +description=srid [, options]])\n\nConstructs a Polygon value using its WKT representation and SRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html +[ST_POLYFROMWKB] +declaration=wkb [, srid [, options]] +category=WKB Functions +description=[, options]])\n\nConstructs a Polygon value using its WKB representation and SRID.\n\nThese functions handle their arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html +[ST_SIMPLIFY] +declaration=g, max_distance +category=MBR Functions +description=Simplifies a geometry using the Douglas-Peucker algorithm and returns a\nsimplified value of the same type.\n\nThe geometry may be any geometry type, although the Douglas-Peucker\nalgorithm may not actually process every type. A geometry collection is\nprocessed by giving its components one by one to the simplification\nalgorithm, and the returned geometries are put into a geometry\ncollection as result.\n\nThe max_distance argument is the distance (in units of the input\ncoordinates) of a vertex to other segments to be removed. Vertices\nwithin this distance of the simplified linestring are removed.\n\nAccording to Boost.Geometry, geometries might become invalid as a\nresult of the simplification process, and the process might create\nself-intersections. To check the validity of the result, pass it to\nST_IsValid().\n\nST_Simplify() handles its arguments as described in the introduction to\nthis section, with this exception:\n\no If the max_distance argument is not positive, or is NaN, an\n ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_wrong_arguments) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-convenience-functions.html +[ST_SRID] +declaration=g [, srid] +category=Geometry Property Functions +description=With a single argument representing a valid geometry object g,\nST_SRID() returns an integer indicating the ID of the spatial reference\nsystem (SRS) associated with g.\n\nWith the optional second argument representing a valid SRID value,\nST_SRID() returns an object with the same type as its first argument\nwith an SRID value equal to the second argument. This only sets the\nSRID value of the object; it does not perform any transformation of\ncoordinate values.\n\nST_SRID() handles its arguments as described in the introduction to\nthis section, with this exception:\n\no For the single-argument syntax, ST_SRID() returns the geometry SRID\n even if it refers to an undefined SRS. An ER_SRS_NOT_FOUND\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_srs_not_found) error does not occur.\n\nST_SRID(g, target_srid) and ST_Transform(g, target_srid) differ as\nfollows:\n\no ST_SRID() changes the geometry SRID value without transforming its\n coordinates.\n\no ST_Transform() transforms the geometry coordinates in addition to\n changing its SRID value.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html +[ST_STARTPOINT] +declaration=ls +category=LineString Property Functions +description=Returns the Point that is the start point of the LineString value ls.\n\nST_StartPoint() handles its arguments as described in the introduction\nto this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-linestring-property-functions.html +[ST_SWAPXY] +declaration=g +category=WKB Functions +description=Accepts an argument in internal geometry format, swaps the X and Y\nvalues of each coordinate pair within the geometry, and returns the\nresult.\n\nST_SwapXY() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-format-conversion-functions.html +[ST_SYMDIFFERENCE] +declaration=g1, g2 +category=GeometryCollection Property Functions +description=Returns a geometry that represents the point set symmetric difference\nof the geometry values g1 and g2, which is defined as:\n\ng1 symdifference g2 := (g1 union g2) difference (g1 intersection g2)\n\nOr, in function call notation:\n\nST_SymDifference(g1, g2) = ST_Difference(ST_Union(g1, g2), ST_Intersection(g1, g2))\n\nThe result is in the same SRS as the geometry arguments.\n\nST_SymDifference() permits arguments in either a Cartesian or a\ngeographic SRS, and handles its arguments as described in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html +[ST_TOUCHES] +declaration=g1, g2 +category=Geometry Relation Functions +description=Two geometries spatially touch if their interiors do not intersect, but\nthe boundary of one of the geometries intersects either the boundary or\nthe interior of the other.\n\nThis function returns 1 or 0 to indicate whether g1 spatially touches\ng2.\n\nST_Touches() handles its arguments as described in the introduction to\nthis section except that the return value is NULL for the additional\ncondition that both geometries are of dimension 0 (Point or\nMultiPoint).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_TRANSFORM] +declaration=g, target_srid +category=GeometryCollection Property Functions +description=Transforms a geometry from one spatial reference system (SRS) to\nanother. The return value is a geometry of the same type as the input\ngeometry with all coordinates transformed to the target SRID,\ntarget_srid. MySQL supports all SRSs defined by EPSG except for those\nlisted here:\n\no EPSG 1042 Krovak Modified\n\no EPSG 1043 Krovak Modified (North Orientated)\n\no EPSG 9816 Tunisia Mining Grid\n\no EPSG 9826 Lambert Conic Conformal (West Orientated)\n\nST_Transform() handles its arguments as described in the introduction\nto this section, with these exceptions:\n\no Geometry arguments that have an SRID value for a geographic SRS do\n not produce an error.\n\no If the geometry or target SRID argument has an SRID value that refers\n to an undefined spatial reference system (SRS), an ER_SRS_NOT_FOUND\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_srs_not_found) error occurs.\n\no If the geometry is in an SRS that ST_Transform() cannot transform\n from, an ER_TRANSFORM_SOURCE_SRS_NOT_SUPPORTED\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_transform_source_srs_not_supported) error occurs.\n\no If the target SRID is in an SRS that ST_Transform() cannot transform\n to, an ER_TRANSFORM_TARGET_SRS_NOT_SUPPORTED\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_transform_target_srs_not_supported) error occurs.\n\no If the geometry is in an SRS that is not WGS 84 and has no TOWGS84\n clause, an ER_TRANSFORM_SOURCE_SRS_MISSING_TOWGS84\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_transform_source_srs_missing_towgs84) error occurs.\n\no If the target SRID is in an SRS that is not WGS 84 and has no TOWGS84\n clause, an ER_TRANSFORM_TARGET_SRS_MISSING_TOWGS84\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n .html#error_er_transform_target_srs_missing_towgs84) error occurs.\n\nST_SRID(g, target_srid) and ST_Transform(g, target_srid) differ as\nfollows:\n\no ST_SRID() changes the geometry SRID value without transforming its\n coordinates.\n ... +[ST_UNION] +declaration=g1, g2 +category=GeometryCollection Property Functions +description=Returns a geometry that represents the point set union of the geometry\nvalues g1 and g2. The result is in the same SRS as the geometry\narguments.\n\nST_Union() permits arguments in either a Cartesian or a geographic SRS,\nand handles its arguments as described in the introduction to this\nsection.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html +[ST_VALIDATE] +declaration=g +category=MBR Functions +description=Validates a geometry according to the OGC specification. A geometry can\nbe syntactically well-formed (WKB value plus SRID) but geometrically\ninvalid. For example, this polygon is geometrically invalid: POLYGON((0\n0, 0 0, 0 0, 0 0, 0 0))\n\nST_Validate() returns the geometry if it is syntactically well-formed\nand is geometrically valid, NULL if the argument is not syntactically\nwell-formed or is not geometrically valid or is NULL.\n\nST_Validate() can be used to filter out invalid geometry data, although\nat a cost. For applications that require more precise results not\ntainted by invalid data, this penalty may be worthwhile.\n\nIf the geometry argument is valid, it is returned as is, except that if\nan input Polygon or MultiPolygon has clockwise rings, those rings are\nreversed before checking for validity. If the geometry is valid, the\nvalue with the reversed rings is returned.\n\nThe only valid empty geometry is represented in the form of an empty\ngeometry collection value. ST_Validate() returns it directly without\nfurther checks in this case.\n\nST_Validate() handles its arguments as described in the introduction to\nthis section, with the exceptions listed here:\n\no If the geometry has a geographic SRS with a longitude or latitude\n that is out of range, an error occurs:\n\n o If a longitude value is not in the range (−180, 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\n o If a latitude value is not in the range [−90, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n ce.html#error_er_geometry_param_latitude_out_of_range) error\n occurs.\n\n Ranges shown are in degrees. The exact range limits deviate slightly\n due to floating-point arithmetic.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-convenience-functions.html +[ST_WITHIN] +declaration=g1, g2 +category=Geometry Relation Functions +description=Returns 1 or 0 to indicate whether g1 is spatially within g2. This\ntests the opposite relationship as ST_Contains().\n\nST_Within() handles its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html +[ST_X] +declaration=p [, new_x_val] +category=Point Property Functions +description=With a single argument representing a valid Point object p, ST_X()\nreturns the X-coordinate value of p as a double-precision number. The X\ncoordinate is considered to refer to the axis that appears first in the\nPoint spatial reference system (SRS) definition.\n\nWith the optional second argument, ST_X() returns a Point object like\nthe first argument with its X coordinate equal to the second argument.\nIf the Point object has a geographic SRS, the second argument must be\nin the proper range for longitude or latitude values.\n\nST_X() handles its arguments as described in the introduction to this\nsection.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-point-property-functions.html +[ST_Y] +declaration=p [, new_y_val] +category=Point Property Functions +description=With a single argument representing a valid Point object p, ST_Y()\nreturns the Y-coordinate value of p as a double-precision number.The Y\ncoordinate is considered to refer to the axis that appears second in\nthe Point spatial reference system (SRS) definition.\n\nWith the optional second argument, ST_Y() returns a Point object like\nthe first argument with its Y coordinate equal to the second argument.\nIf the Point object has a geographic SRS, the second argument must be\nin the proper range for longitude or latitude values.\n\nST_Y() handles its arguments as described in the introduction to this\nsection.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-point-property-functions.html +[SUBDATE] +declaration=date,INTERVAL expr unit +category=Date and Time Functions +description=When invoked with the INTERVAL form of the second argument, SUBDATE()\nis a synonym for DATE_SUB(). For information on the INTERVAL unit\nargument, see the discussion for DATE_ADD().\n\nmysql> SELECT DATE_SUB('2008-01-02', INTERVAL 31 DAY);\n -> '2007-12-02'\nmysql> SELECT SUBDATE('2008-01-02', INTERVAL 31 DAY);\n -> '2007-12-02'\n\nThe second form enables the use of an integer value for days. In such\ncases, it is interpreted as the number of days to be subtracted from\nthe date or datetime expression expr.\n\nmysql> SELECT SUBDATE('2008-01-02 12:00:00', 31);\n -> '2007-12-02 12:00:00'\n\nThis function returns NULL if any of its arguments are NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[SUBSTR] +declaration=str,pos +category=String Functions +description=FROM pos FOR len)\n\nSUBSTR() is a synonym for SUBSTRING().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[SUBSTRING] +declaration=str,pos +category=String Functions +description=SUBSTRING(str FROM pos FOR len)\n\nThe forms without a len argument return a substring from string str\nstarting at position pos. The forms with a len argument return a\nsubstring len characters long from string str, starting at position\npos. The forms that use FROM are standard SQL syntax. It is also\npossible to use a negative value for pos. In this case, the beginning\nof the substring is pos characters from the end of the string, rather\nthan the beginning. A negative value may be used for pos in any of the\nforms of this function. A value of 0 for pos returns an empty string.\n\nFor all forms of SUBSTRING(), the position of the first character in\nthe string from which the substring is to be extracted is reckoned as\n1.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[SUBSTRING_INDEX] +declaration=str,delim,count +category=String Functions +description=Returns the substring from string str before count occurrences of the\ndelimiter delim. If count is positive, everything to the left of the\nfinal delimiter (counting from the left) is returned. If count is\nnegative, everything to the right of the final delimiter (counting from\nthe right) is returned. SUBSTRING_INDEX() performs a case-sensitive\nmatch when searching for delim.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[SUBTIME] +declaration=expr1,expr2 +category=Date and Time Functions +description=SUBTIME() returns expr1 − expr2 expressed as a value in the same\nformat as expr1. expr1 is a time or datetime expression, and expr2 is a\ntime expression.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[SUM] +declaration=[DISTINCT] expr +category=Aggregate Functions and Modifiers +description=Returns the sum of expr. If the return set has no rows, SUM() returns\nNULL. The DISTINCT keyword can be used to sum only the distinct values\nof expr.\n\nIf there are no matching rows, or if expr is NULL, SUM() returns NULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html; it\ncannot be used with DISTINCT.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[SYSDATE] +declaration=[fsp] +category=Date and Time Functions +description=Returns the current date and time as a value in 'YYYY-MM-DD hh:mm:ss'\nor YYYYMMDDhhmmss format, depending on whether the function is used in\nstring or numeric context.\n\nIf the fsp argument is given to specify a fractional seconds precision\nfrom 0 to 6, the return value includes a fractional seconds part of\nthat many digits.\n\nSYSDATE() returns the time at which it executes. This differs from the\nbehavior for NOW(), which returns a constant time that indicates the\ntime at which the statement began to execute. (Within a stored function\nor trigger, NOW() returns the time at which the function or triggering\nstatement began to execute.)\n\nmysql> SELECT NOW(), SLEEP(2), NOW();\n+---------------------+----------+---------------------+\n| NOW() | SLEEP(2) | NOW() |\n+---------------------+----------+---------------------+\n| 2006-04-12 13:47:36 | 0 | 2006-04-12 13:47:36 |\n+---------------------+----------+---------------------+\n\nmysql> SELECT SYSDATE(), SLEEP(2), SYSDATE();\n+---------------------+----------+---------------------+\n| SYSDATE() | SLEEP(2) | SYSDATE() |\n+---------------------+----------+---------------------+\n| 2006-04-12 13:47:44 | 0 | 2006-04-12 13:47:46 |\n+---------------------+----------+---------------------+\n\nIn addition, the SET TIMESTAMP statement affects the value returned by\nNOW() but not by SYSDATE(). This means that timestamp settings in the\nbinary log have no effect on invocations of SYSDATE().\n\nBecause SYSDATE() can return different values even within the same\nstatement, and is not affected by SET TIMESTAMP, it is nondeterministic\nand therefore unsafe for replication if statement-based binary logging\nis used. If that is a problem, you can use row-based logging.\n\nAlternatively, you can use the --sysdate-is-now option to cause\nSYSDATE() to be an alias for NOW(). This works if the option is used on\nboth the replication source server and the replica.\n\nThe nondeterministic nature of SYSDATE() also means that indexes cannot\nbe used for evaluating expressions that refer to it.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[SYSTEM_USER] +declaration= +category=Information Functions +description=SYSTEM_USER() is a synonym for USER().\n\n*Note*:\n\nThe SYSTEM_USER() function is distinct from the SYSTEM_USER privilege.\nThe former returns the current MySQL account name. The latter\ndistinguishes the system user and regular user account categories (see\nhttps://dev.mysql.com/doc/refman/8.3/en/account-categories.html).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[TAN] +declaration=X +category=Numeric Functions +description=Returns the tangent of X, where X is given in radians. Returns NULL if\nX is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[TEXT] +declaration=M +category=Data Types +description=A TEXT column with a maximum length of 65,535 (216 − 1) characters.\nThe effective maximum length is less if the value contains multibyte\ncharacters. Each TEXT value is stored using a 2-byte length prefix that\nindicates the number of bytes in the value.\n\nAn optional length M can be given for this type. If this is done, MySQL\ncreates the column as the smallest TEXT type large enough to hold\nvalues M characters long.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html +[TIME] +declaration=fsp +category=Data Types +description=A time. The range is '-838:59:59.000000' to '838:59:59.000000'. MySQL\ndisplays TIME values in 'hh:mm:ss[.fraction]' format, but permits\nassignment of values to TIME columns using either strings or numbers.\n\nAn optional fsp value in the range from 0 to 6 may be given to specify\nfractional seconds precision. A value of 0 signifies that there is no\nfractional part. If omitted, the default precision is 0.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-type-syntax.html +[TIMEDIFF] +declaration=expr1,expr2 +category=Date and Time Functions +description=TIMEDIFF() returns expr1 − expr2 expressed as a time value. expr1 and\nexpr2 are strings which are converted to TIME or DATETIME expressions;\nthese must be of the same type following conversion. Returns NULL if\nexpr1 or expr2 is NULL.\n\nThe result returned by TIMEDIFF() is limited to the range allowed for\nTIME values. Alternatively, you can use either of the functions\nTIMESTAMPDIFF() and UNIX_TIMESTAMP(), both of which return integers.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[TIMESTAMP] +declaration=fsp +category=Data Types +description=A timestamp. The range is '1970-01-01 00:00:01.000000' UTC to\n'2038-01-19 03:14:07.499999' UTC. TIMESTAMP values are stored as the\nnumber of seconds since the epoch ('1970-01-01 00:00:00' UTC). A\nTIMESTAMP cannot represent the value '1970-01-01 00:00:00' because that\nis equivalent to 0 seconds from the epoch and the value 0 is reserved\nfor representing '0000-00-00 00:00:00', the "zero" TIMESTAMP value.\n\nAn optional fsp value in the range from 0 to 6 may be given to specify\nfractional seconds precision. A value of 0 signifies that there is no\nfractional part. If omitted, the default precision is 0.\n\nThe way the server handles TIMESTAMP definitions depends on the value\nof the explicit_defaults_for_timestamp system variable (see\nhttps://dev.mysql.com/doc/refman/8.3/en/server-system-variables.html).\n\nIf explicit_defaults_for_timestamp is enabled, there is no automatic\nassignment of the DEFAULT CURRENT_TIMESTAMP or ON UPDATE\nCURRENT_TIMESTAMP attributes to any TIMESTAMP column. They must be\nincluded explicitly in the column definition. Also, any TIMESTAMP not\nexplicitly declared as NOT NULL permits NULL values.\n\nIf explicit_defaults_for_timestamp is disabled, the server handles\nTIMESTAMP as follows:\n\nUnless specified otherwise, the first TIMESTAMP column in a table is\ndefined to be automatically set to the date and time of the most recent\nmodification if not explicitly assigned a value. This makes TIMESTAMP\nuseful for recording the timestamp of an INSERT or UPDATE operation.\nYou can also set any TIMESTAMP column to the current date and time by\nassigning it a NULL value, unless it has been defined with the NULL\nattribute to permit NULL values.\n\nAutomatic initialization and updating to the current date and time can\nbe specified using DEFAULT CURRENT_TIMESTAMP and ON UPDATE\nCURRENT_TIMESTAMP column definition clauses. By default, the first\nTIMESTAMP column has these properties, as previously noted. However,\nany TIMESTAMP column in a table can be defined to have these\nproperties.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-type-syntax.html +[TIMESTAMPADD] +declaration=unit,interval,datetime_expr +category=Date and Time Functions +description=Adds the integer expression interval to the date or datetime expression\ndatetime_expr. The unit for interval is given by the unit argument,\nwhich should be one of the following values: MICROSECOND\n(microseconds), SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, or\nYEAR.\n\nThe unit value may be specified using one of keywords as shown, or with\na prefix of SQL_TSI_. For example, DAY and SQL_TSI_DAY both are legal.\n\nThis function returns NULL if interval or datetime_expr is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[TIMESTAMPDIFF] +declaration=unit,datetime_expr1,datetime_expr2 +category=Date and Time Functions +description=Returns datetime_expr2 − datetime_expr1, where datetime_expr1 and\ndatetime_expr2 are date or datetime expressions. One expression may be\na date and the other a datetime; a date value is treated as a datetime\nhaving the time part '00:00:00' where necessary. The unit for the\nresult (an integer) is given by the unit argument. The legal values for\nunit are the same as those listed in the description of the\nTIMESTAMPADD() function.\n\nThis function returns NULL if datetime_expr1 or datetime_expr2 is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[TIME_FORMAT] +declaration=time,format +category=Date and Time Functions +description=This is used like the DATE_FORMAT() function, but the format string may\ncontain format specifiers only for hours, minutes, seconds, and\nmicroseconds. Other specifiers produce a NULL or 0. TIME_FORMAT()\nreturns NULL if time or format is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[TIME_TO_SEC] +declaration=time +category=Date and Time Functions +description=Returns the time argument, converted to seconds. Returns NULL if time\nis NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[TINYINT] +declaration=M +category=Data Types +description=A very small integer. The signed range is -128 to 127. The unsigned\nrange is 0 to 255.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html +[TO_BASE64] +declaration=str +category=String Functions +description=Converts the string argument to base-64 encoded form and returns the\nresult as a character string with the connection character set and\ncollation. If the argument is not a string, it is converted to a string\nbefore conversion takes place. The result is NULL if the argument is\nNULL. Base-64 encoded strings can be decoded using the FROM_BASE64()\nfunction.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[TO_DAYS] +declaration=date +category=Date and Time Functions +description=Given a date date, returns a day number (the number of days since year\n0). Returns NULL if date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[TO_SECONDS] +declaration=expr +category=Date and Time Functions +description=Given a date or datetime expr, returns the number of seconds since the\nyear 0. If expr is not a valid date or datetime value (including NULL),\nit returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[TRIM] +declaration=[{BOTH | LEADING | TRAILING} [remstr] FROM] str +category=String Functions +description=FROM] str)\n\nReturns the string str with all remstr prefixes or suffixes removed. If\nnone of the specifiers BOTH, LEADING, or TRAILING is given, BOTH is\nassumed. remstr is optional and, if not specified, spaces are removed.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[TRUNCATE] +declaration=X,D +category=Numeric Functions +description=Returns the number X, truncated to D decimal places. If D is 0, the\nresult has no decimal point or fractional part. D can be negative to\ncause D digits left of the decimal point of the value X to become zero.\nIf X or D is NULL, the function returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html +[UCASE] +declaration=str +category=String Functions +description=UCASE() is a synonym for UPPER().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[UNCOMPRESS] +declaration=string_to_uncompress +category=Encryption Functions +description=Uncompresses a string compressed by the COMPRESS() function. If the\nargument is not a compressed value, the result is NULL; if\nstring_to_uncompress is NULL, the result is also NULL. This function\nrequires MySQL to have been compiled with a compression library such as\nzlib. Otherwise, the return value is always NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[UNCOMPRESSED_LENGTH] +declaration=compressed_string +category=Encryption Functions +description=Returns the length that the compressed string had before being\ncompressed. Returns NULL if compressed_string is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[UNHEX] +declaration=str +category=String Functions +description=For a string argument str, UNHEX(str) interprets each pair of\ncharacters in the argument as a hexadecimal number and converts it to\nthe byte represented by the number. The return value is a binary\nstring.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[UNIX_TIMESTAMP] +declaration=[date] +category=Date and Time Functions +description=If UNIX_TIMESTAMP() is called with no date argument, it returns a Unix\ntimestamp representing seconds since '1970-01-01 00:00:00' UTC.\n\nIf UNIX_TIMESTAMP() is called with a date argument, it returns the\nvalue of the argument as seconds since '1970-01-01 00:00:00' UTC. The\nserver interprets date as a value in the session time zone and converts\nit to an internal Unix timestamp value in UTC. (Clients can set the\nsession time zone as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/time-zone-support.html.) The\ndate argument may be a DATE, DATETIME, or TIMESTAMP string, or a number\nin YYMMDD, YYMMDDhhmmss, YYYYMMDD, or YYYYMMDDhhmmss format. If the\nargument includes a time part, it may optionally include a fractional\nseconds part.\n\nThe return value is an integer if no argument is given or the argument\ndoes not include a fractional seconds part, or DECIMAL if an argument\nis given that includes a fractional seconds part.\n\nWhen the date argument is a TIMESTAMP column, UNIX_TIMESTAMP() returns\nthe internal timestamp value directly, with no implicit\n"string-to-Unix-timestamp" conversion.\n\nThe valid range of argument values is the same as for the TIMESTAMP\ndata type: '1970-01-01 00:00:01.000000' UTC to '2038-01-19\n03:14:07.999999' UTC for 32-bit platforms; for MySQL running on 64-bit\nplatforms, the valid range of argument values for UNIX_TIMESTAMP() is\n'1970-01-01 00:00:01.000000' UTC to '3001-01-19 03:14:07.999999' UTC\n(corresponding to 32536771199.999999 seconds).\n\nRegardless of MySQL version or platform architecture, if you pass an\nout-of-range date to UNIX_TIMESTAMP(), it returns 0. If date is NULL,\nit returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[UPDATEXML] +declaration=xml_target, xpath_expr, new_xml +category=XML +description=This function replaces a single portion of a given fragment of XML\nmarkup xml_target with a new XML fragment new_xml, and then returns the\nchanged XML. The portion of xml_target that is replaced matches an\nXPath expression xpath_expr supplied by the user.\n\nIf no expression matching xpath_expr is found, or if multiple matches\nare found, the function returns the original xml_target XML fragment.\nAll three arguments should be strings. If any of the arguments to\nUpdateXML() are NULL, the function returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/xml-functions.html +[UPPER] +declaration=str +category=String Functions +description=Returns the string str with all characters changed to uppercase\naccording to the current character set mapping, or NULL if str is NULL.\nThe default character set is utf8mb4.\n\nmysql> SELECT UPPER('Hej');\n -> 'HEJ'\n\nSee the description of LOWER() for information that also applies to\nUPPER(). This included information about how to perform lettercase\nconversion of binary strings (BINARY, VARBINARY, BLOB) for which these\nfunctions are ineffective, and information about case folding for\nUnicode character sets.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html +[USER] +declaration= +category=Information Functions +description=Returns the current MySQL user name and host name as a string in the\nutf8mb3 character set.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[UTC_DATE] +declaration= +category=Date and Time Functions +description=Returns the current UTC date as a value in 'YYYY-MM-DD' or YYYYMMDD\nformat, depending on whether the function is used in string or numeric\ncontext.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[UTC_TIME] +declaration=[fsp] +category=Date and Time Functions +description=Returns the current UTC time as a value in 'hh:mm:ss' or hhmmss format,\ndepending on whether the function is used in string or numeric context.\n\nIf the fsp argument is given to specify a fractional seconds precision\nfrom 0 to 6, the return value includes a fractional seconds part of\nthat many digits.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[UTC_TIMESTAMP] +declaration=[fsp] +category=Date and Time Functions +description=Returns the current UTC date and time as a value in 'YYYY-MM-DD\nhh:mm:ss' or YYYYMMDDhhmmss format, depending on whether the function\nis used in string or numeric context.\n\nIf the fsp argument is given to specify a fractional seconds precision\nfrom 0 to 6, the return value includes a fractional seconds part of\nthat many digits.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[UUID] +declaration= +category=Miscellaneous Functions +description=Returns a Universal Unique Identifier (UUID) generated according to RFC\n4122, "A Universally Unique IDentifier (UUID) URN Namespace"\n(http://www.ietf.org/rfc/rfc4122.txt).\n\nA UUID is designed as a number that is globally unique in space and\ntime. Two calls to UUID() are expected to generate two different\nvalues, even if these calls are performed on two separate devices not\nconnected to each other.\n\n*Warning*:\n\nAlthough UUID() values are intended to be unique, they are not\nnecessarily unguessable or unpredictable. If unpredictability is\nrequired, UUID values should be generated some other way.\n\nUUID() returns a value that conforms to UUID version 1 as described in\nRFC 4122. The value is a 128-bit number represented as a utf8mb3 string\nof five hexadecimal numbers in aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\nformat:\n\no The first three numbers are generated from the low, middle, and high\n parts of a timestamp. The high part also includes the UUID version\n number.\n\no The fourth number preserves temporal uniqueness in case the timestamp\n value loses monotonicity (for example, due to daylight saving time).\n\no The fifth number is an IEEE 802 node number that provides spatial\n uniqueness. A random number is substituted if the latter is not\n available (for example, because the host device has no Ethernet card,\n or it is unknown how to find the hardware address of an interface on\n the host operating system). In this case, spatial uniqueness cannot\n be guaranteed. Nevertheless, a collision should have very low\n probability.\n\n The MAC address of an interface is taken into account only on\n FreeBSD, Linux, and Windows. On other operating systems, MySQL uses a\n randomly generated 48-bit number.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[UUID_SHORT] +declaration= +category=Miscellaneous Functions +description=Returns a "short" universal identifier as a 64-bit unsigned integer.\nValues returned by UUID_SHORT() differ from the string-format 128-bit\nidentifiers returned by the UUID() function and have different\nuniqueness properties. The value of UUID_SHORT() is guaranteed to be\nunique if the following conditions hold:\n\no The server_id value of the current server is between 0 and 255 and is\n unique among your set of source and replica servers\n\no You do not set back the system time for your server host between\n mysqld restarts\n\no You invoke UUID_SHORT() on average fewer than 16 million times per\n second between mysqld restarts\n\nThe UUID_SHORT() return value is constructed this way:\n\n (server_id & 255) << 56\n+ (server_startup_time_in_seconds << 24)\n+ incremented_variable++;\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[UUID_TO_BIN] +declaration=string_uuid +category=Miscellaneous Functions +description=Converts a string UUID to a binary UUID and returns the result. (The\nIS_UUID() function description lists the permitted string UUID\nformats.) The return binary UUID is a VARBINARY(16) value. If the UUID\nargument is NULL, the return value is NULL. If any argument is invalid,\nan error occurs.\n\nUUID_TO_BIN() takes one or two arguments:\n\no The one-argument form takes a string UUID value. The binary result is\n in the same order as the string argument.\n\no The two-argument form takes a string UUID value and a flag value:\n\n o If swap_flag is 0, the two-argument form is equivalent to the\n one-argument form. The binary result is in the same order as the\n string argument.\n\n o If swap_flag is 1, the format of the return value differs: The\n time-low and time-high parts (the first and third groups of\n hexadecimal digits, respectively) are swapped. This moves the more\n rapidly varying part to the right and can improve indexing\n efficiency if the result is stored in an indexed column.\n\nTime-part swapping assumes the use of UUID version 1 values, such as\nare generated by the UUID() function. For UUID values produced by other\nmeans that do not follow version 1 format, time-part swapping provides\nno benefit. For details about version 1 format, see the UUID() function\ndescription.\n\nSuppose that you have the following string UUID value:\n\nmysql> SET @uuid = '6ccd780c-baba-1026-9564-5b8c656024db';\n\nTo convert the string UUID to binary with or without time-part\nswapping, use UUID_TO_BIN():\n\nmysql> SELECT HEX(UUID_TO_BIN(@uuid));\n+----------------------------------+\n| HEX(UUID_TO_BIN(@uuid)) |\n+----------------------------------+\n| 6CCD780CBABA102695645B8C656024DB |\n+----------------------------------+\nmysql> SELECT HEX(UUID_TO_BIN(@uuid, 0));\n+----------------------------------+\n| HEX(UUID_TO_BIN(@uuid, 0)) |\n+----------------------------------+\n| 6CCD780CBABA102695645B8C656024DB |\n+----------------------------------+\nmysql> SELECT HEX(UUID_TO_BIN(@uuid, 1));\n+----------------------------------+\n ... +[VALIDATE_PASSWORD_STRENGTH] +declaration=str +category=Encryption Functions +description=Given an argument representing a plaintext password, this function\nreturns an integer to indicate how strong the password is, or NULL if\nthe argument is NULL. The return value ranges from 0 (weak) to 100\n(strong).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html +[VALUES] +declaration=col_name +category=Miscellaneous Functions +description=In an INSERT ... ON DUPLICATE KEY UPDATE statement, you can use the\nVALUES(col_name) function in the UPDATE clause to refer to column\nvalues from the INSERT portion of the statement. In other words,\nVALUES(col_name) in the UPDATE clause refers to the value of col_name\nthat would be inserted, had no duplicate-key conflict occurred. This\nfunction is especially useful in multiple-row inserts. The VALUES()\nfunction is meaningful only in the ON DUPLICATE KEY UPDATE clause of\nINSERT statements and returns NULL otherwise. See\nhttps://dev.mysql.com/doc/refman/8.3/en/insert-on-duplicate.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html +[VARBINARY] +declaration=M +category=Data Types +description=The VARBINARY type is similar to the VARCHAR type, but stores binary\nbyte strings rather than nonbinary character strings. M represents the\nmaximum column length in bytes.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html +[VARCHAR] +declaration=M +category=Data Types +description=collation_name]\n\nA variable-length string. M represents the maximum column length in\ncharacters. The range of M is 0 to 65,535. The effective maximum length\nof a VARCHAR is subject to the maximum row size (65,535 bytes, which is\nshared among all columns) and the character set used. For example,\nutf8mb3 characters can require up to three bytes per character, so a\nVARCHAR column that uses the utf8mb3 character set can be declared to\nbe a maximum of 21,844 characters. See\nhttps://dev.mysql.com/doc/refman/8.3/en/column-count-limit.html.\n\nMySQL stores VARCHAR values as a 1-byte or 2-byte length prefix plus\ndata. The length prefix indicates the number of bytes in the value. A\nVARCHAR column uses one length byte if values require no more than 255\nbytes, two length bytes if values may require more than 255 bytes.\n\n*Note*:\n\nMySQL follows the standard SQL specification, and does not remove\ntrailing spaces from VARCHAR values.\n\nVARCHAR is shorthand for CHARACTER VARYING. NATIONAL VARCHAR is the\nstandard SQL way to define that a VARCHAR column should use some\npredefined character set. MySQL uses utf8mb3 as this predefined\ncharacter set.\nhttps://dev.mysql.com/doc/refman/8.3/en/charset-national.html. NVARCHAR\nis shorthand for NATIONAL VARCHAR.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html +[VARIANCE] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns the population standard variance of expr. VARIANCE() is a\nsynonym for the standard SQL function VAR_POP(), provided as a MySQL\nextension.\n\nIf there are no matching rows, or if expr is NULL, VARIANCE() returns\nNULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[VAR_POP] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns the population standard variance of expr. It considers rows as\nthe whole population, not as a sample, so it has the number of rows as\nthe denominator. You can also use VARIANCE(), which is equivalent but\nis not standard SQL.\n\nIf there are no matching rows, or if expr is NULL, VAR_POP() returns\nNULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[VAR_SAMP] +declaration=expr +category=Aggregate Functions and Modifiers +description=Returns the sample variance of expr. That is, the denominator is the\nnumber of rows minus one.\n\nIf there are no matching rows, or if expr is NULL, VAR_SAMP() returns\nNULL.\n\nThis function executes as a window function if over_clause is present.\nover_clause is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html +[VERSION] +declaration= +category=Information Functions +description=Returns a string that indicates the MySQL server version. The string\nuses the utf8mb3 character set. The value might have a suffix in\naddition to the version number. See the description of the version\nsystem variable in\nhttps://dev.mysql.com/doc/refman/8.3/en/server-system-variables.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html +[WAIT_FOR_EXECUTED_GTID_SET] +declaration=gtid_set[, timeout] +category=GTID +description=Wait until the server has applied all of the transactions whose global\ntransaction identifiers are contained in gtid_set; that is, until the\ncondition GTID_SUBSET(gtid_subset, @@GLOBAL.gtid_executed) holds. See\nhttps://dev.mysql.com/doc/refman/8.3/en/replication-gtids-concepts.html\nfor a definition of GTID sets.\n\nIf a timeout is specified, and timeout seconds elapse before all of the\ntransactions in the GTID set have been applied, the function stops\nwaiting. timeout is optional, and the default timeout is 0 seconds, in\nwhich case the function always waits until all of the transactions in\nthe GTID set have been applied. timeout must be greater than or equal\nto 0; when running in strict SQL mode, a negative timeout value is\nimmediately rejected with an error (ER_WRONG_ARGUMENTS\n(https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html\n#error_er_wrong_arguments)); otherwise the function returns NULL,\nand raises a warning.\n\nWAIT_FOR_EXECUTED_GTID_SET() monitors all the GTIDs that are applied on\nthe server, including transactions that arrive from all replication\nchannels and user clients. It does not take into account whether\nreplication channels have been started or stopped.\n\nFor more information, see\nhttps://dev.mysql.com/doc/refman/8.3/en/replication-gtids.html.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gtid-functions.html +[WEEK] +declaration=date[,mode] +category=Date and Time Functions +description=This function returns the week number for date. The two-argument form\nof WEEK() enables you to specify whether the week starts on Sunday or\nMonday and whether the return value should be in the range from 0 to 53\nor from 1 to 53. If the mode argument is omitted, the value of the\ndefault_week_format system variable is used. See\nhttps://dev.mysql.com/doc/refman/8.3/en/server-system-variables.html.\nFor a NULL date value, the function returns NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[WEEKDAY] +declaration=date +category=Date and Time Functions +description=Returns the weekday index for date (0 = Monday, 1 = Tuesday, ... 6 =\nSunday). Returns NULL if date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[WEEKOFYEAR] +declaration=date +category=Date and Time Functions +description=Returns the calendar week of the date as a number in the range from 1\nto 53. Returns NULL if date is NULL.\n\nWEEKOFYEAR() is a compatibility function that is equivalent to\nWEEK(date,3).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[WEIGHT_STRING] +declaration=str [AS {CHAR|BINARY}(N +category=String Functions +description=This function returns the weight string for the input string. The\nreturn value is a binary string that represents the comparison and\nsorting value of the string, or NULL if the argument is NULL. It has\nthese properties:\n\no If WEIGHT_STRING(str1) = WEIGHT_STRING(str2), then str1 = str2 (str1\n and str2 are considered equal)\n\no If WEIGHT_STRING(str1) < WEIGHT_STRING(str2), then str1 < str2 (str1\n sorts before str2)\n\nWEIGHT_STRING() is a debugging function intended for internal use. Its\nbehavior can change without notice between MySQL versions. It can be\nused for testing and debugging of collations, especially if you are\nadding a new collation. See\nhttps://dev.mysql.com/doc/refman/8.3/en/adding-collation.html.\n\nThis list briefly summarizes the arguments. More details are given in\nthe discussion following the list.\n\no str: The input string expression.\n\no AS clause: Optional; cast the input string to a given type and\n length.\n\no flags: Optional; unused.\n\nThe input string, str, is a string expression. If the input is a\nnonbinary (character) string such as a CHAR, VARCHAR, or TEXT value,\nthe return value contains the collation weights for the string. If the\ninput is a binary (byte) string such as a BINARY, VARBINARY, or BLOB\nvalue, the return value is the same as the input (the weight for each\nbyte in a binary string is the byte value). If the input is NULL,\nWEIGHT_STRING() returns NULL.\n\nExamples:\n\nmysql> SET @s = _utf8mb4 'AB' COLLATE utf8mb4_0900_ai_ci;\nmysql> SELECT @s, HEX(@s), HEX(WEIGHT_STRING(@s));\n+------+---------+------------------------+\n| @s | HEX(@s) | HEX(WEIGHT_STRING(@s)) |\n+------+---------+------------------------+\n| AB | 4142 | 1C471C60 |\n+------+---------+------------------------+\n\nmysql> SET @s = _utf8mb4 'ab' COLLATE utf8mb4_0900_ai_ci;\nmysql> SELECT @s, HEX(@s), HEX(WEIGHT_STRING(@s));\n+------+---------+------------------------+\n| @s | HEX(@s) | HEX(WEIGHT_STRING(@s)) |\n+------+---------+------------------------+\n ... +[YEAR] +declaration=date +category=Date and Time Functions +description=Returns the year for date, in the range 1000 to 9999, or 0 for the\n"zero" date. Returns NULL if date is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html +[YEARWEEK] +declaration=date +category=Date and Time Functions +description=Returns year and week for a date. The year in the result may be\ndifferent from the year in the date argument for the first and the last\nweek of the year. Returns NULL if date is NULL.\n\nThe mode argument works exactly like the mode argument to WEEK(). For\nthe single-argument syntax, a mode value of 0 is used. Unlike WEEK(),\nthe value of default_week_format does not influence YEARWEEK().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html \ No newline at end of file diff --git a/out/functions-postgresql.ini b/out/functions-postgresql.ini index 71a880ab6..bf2a8d0b6 100644 --- a/out/functions-postgresql.ini +++ b/out/functions-postgresql.ini @@ -1,204 +1,2244 @@ +[ABBREV] +declaration=inet +category=Network Address Functions +description=Creates an abbreviated display format as text. (The result is the same as\nthe inet output function produces; it is "abbreviated" only in comparison\nto the result of an explicit cast to text, which for historical reasons\nwill never suppress the netmask part.) [ABS] -declaration=number +declaration=numeric_type category=Numeric/Math Functions -description=The PostgreSQL abs function returns the absolute value of a number. -[AGE] -declaration=date1 and date2 -category=Date/Time Functions -description=The PostgreSQL age function returns the number of years, months, and days between two dates. -[AVG] -declaration=expression1, expression2, ... expression_n,aggregate_expression,tables,WHERE conditions +description=Absolute value +[ACLDEFAULT] +declaration=type "char", ownerId oid +category=Session Information Functions +description=Constructs an aclitem array holding the default access privileges for an\nobject of type type belonging to the role with OID ownerId. This represents\nthe access privileges that will be assumed when an object's ACL entry is\nnull. (The default access privileges are described in Section 5.8.) The\ntype parameter must be one of 'c' for COLUMN, 'r' for TABLE and table-like\nobjects, 's' for SEQUENCE, 'd' for DATABASE, 'f' for FUNCTION or PROCEDURE,\n'l' for LANGUAGE, 'L' for LARGE OBJECT, 'n' for SCHEMA, 'p' for PARAMETER,\n't' for TABLESPACE, 'F' for FOREIGN DATA WRAPPER, 'S' for FOREIGN SERVER,\nor 'T' for TYPE or DOMAIN. +[ACLEXPLODE] +declaration=aclitem[] +category=Session Information Functions +description=Returns the aclitem array as a set of rows. If the grantee is the\npseudo-role PUBLIC, it is represented by zero in the grantee column. Each\ngranted privilege is represented as SELECT, INSERT, etc (see Table 5.1 for\na full list). Note that each privilege is broken out as a separate row, so\nonly one keyword appears in the privilege_type column. +[ACOS] +declaration=double precision +category=Numeric/Math Functions +description=Inverse cosine, result in radians +[ACOSD] +declaration=double precision +category=Numeric/Math Functions +description=Inverse cosine, result in degrees +[ACOSH] +declaration=double precision category=Numeric/Math Functions -description=The PostgreSQL avg function returns the average value of an expression. -[BTRIM] -declaration=string,trim_character +description=Inverse hyperbolic cosine +[AGE1] +name=AGE +declaration=timestamp, timestamp +category=Date/Time Functions +description=Subtract arguments, producing a "symbolic" result that uses years and\nmonths, rather than just days +[AGE2] +name=AGE +declaration=xid +category=Session Information Functions +description=Returns the number of transactions between the supplied transaction id and\nthe current transaction counter. +[ANY_VALUE] +declaration=anyelement +category=Aggregate Functions +description=Returns an arbitrary value from the non-null input values. +[AREA] +declaration=geometric_type +category=Geometric Functions +description=Computes area. Available for box, path, circle. A path input must be\nclosed, else NULL is returned. Also, if the path is self-intersecting, the\nresult may be meaningless. +[ARRAY_AGG] +declaration=anynonarray ORDER BY input_sort_columns +category=Aggregate Functions +description=Collects all the input values, including nulls, into an array. +[ARRAY_APPEND] +declaration=anycompatiblearray, anycompatible +category=Array Functions +description=Appends an element to the end of an array (same as the anycompatiblearray\n|| anycompatible operator). +[ARRAY_CAT] +declaration=anycompatiblearray, anycompatiblearray +category=Array Functions +description=Concatenates two arrays (same as the anycompatiblearray ||\nanycompatiblearray operator). +[ARRAY_DIMS] +declaration=anyarray +category=Array Functions +description=Returns a text representation of the array's dimensions. +[ARRAY_FILL] +declaration=anyelement, integer[] [, integer[] ] +category=Array Functions +description=Returns an array filled with copies of the given value, having dimensions\nof the lengths specified by the second argument. The optional third\nargument supplies lower-bound values for each dimension (which default to\nall 1). +[ARRAY_LENGTH] +declaration=anyarray, integer +category=Array Functions +description=Returns the length of the requested array dimension. (Produces NULL instead\nof 0 for empty or missing array dimensions.) +[ARRAY_LOWER] +declaration=anyarray, integer +category=Array Functions +description=Returns the lower bound of the requested array dimension. +[ARRAY_NDIMS] +declaration=anyarray +category=Array Functions +description=Returns the number of dimensions of the array. +[ARRAY_POSITION] +declaration=anycompatiblearray, anycompatible [, integer ] +category=Array Functions +description=Returns the subscript of the first occurrence of the second argument in the\narray, or NULL if it's not present. If the third argument is given, the\nsearch begins at that subscript. The array must be one-dimensional.\nComparisons are done using IS NOT DISTINCT FROM semantics, so it is\npossible to search for NULL. +[ARRAY_POSITIONS] +declaration=anycompatiblearray, anycompatible +category=Array Functions +description=Returns an array of the subscripts of all occurrences of the second\nargument in the array given as first argument. The array must be\none-dimensional. Comparisons are done using IS NOT DISTINCT FROM semantics,\nso it is possible to search for NULL. NULL is returned only if the array is\nNULL; if the value is not found in the array, an empty array is returned. +[ARRAY_PREPEND] +declaration=anycompatible, anycompatiblearray +category=Array Functions +description=Prepends an element to the beginning of an array (same as the anycompatible\n|| anycompatiblearray operator). +[ARRAY_REMOVE] +declaration=anycompatiblearray, anycompatible +category=Array Functions +description=Removes all elements equal to the given value from the array. The array\nmust be one-dimensional. Comparisons are done using IS NOT DISTINCT FROM\nsemantics, so it is possible to remove NULLs. +[ARRAY_REPLACE] +declaration=anycompatiblearray, anycompatible, anycompatible +category=Array Functions +description=Replaces each array element equal to the second argument with the third\nargument. +[ARRAY_SAMPLE] +declaration=array anyarray, n integer +category=Array Functions +description=Returns an array of n items randomly selected from array. n may not exceed\nthe length of array's first dimension. If array is multi-dimensional, an\n"item" is a slice having a given first subscript. +[ARRAY_SHUFFLE] +declaration=anyarray +category=Array Functions +description=Randomly shuffles the first dimension of the array. +[ARRAY_TO_JSON] +declaration=anyarray [, boolean ] +category=JSON Functions +description=Converts an SQL array to a JSON array. The behavior is the same as to_json\nexcept that line feeds will be added between top-level array elements if\nthe optional boolean parameter is true. +[ARRAY_TO_STRING] +declaration=array anyarray, delimiter text [, null_string text ] +category=Array Functions +description=Converts each array element to its text representation, and concatenates\nthose separated by the delimiter string. If null_string is given and is not\nNULL, then NULL array entries are represented by that string; otherwise,\nthey are omitted. See also string_to_array. +[ARRAY_TO_TSVECTOR] +declaration=text[] +category=Text Search Functions +description=Converts an array of text strings to a tsvector. The given strings are used\nas lexemes as-is, without further processing. Array elements must not be\nempty strings or NULL. +[ARRAY_UPPER] +declaration=anyarray, integer +category=Array Functions +description=Returns the upper bound of the requested array dimension. +[ASCII] +declaration=text category=String Functions -description=The PostgreSQL btrim function removes all specified characters from both the beginning and the end of a string. -[CEILING] -declaration=number +description=Returns the numeric code of the first character of the argument. In UTF8\nencoding, returns the Unicode code point of the character. In other\nmultibyte encodings, the argument must be an ASCII character. +[ASIN] +declaration=double precision +category=Numeric/Math Functions +description=Inverse sine, result in radians +[ASIND] +declaration=double precision +category=Numeric/Math Functions +description=Inverse sine, result in degrees +[ASINH] +declaration=double precision +category=Numeric/Math Functions +description=Inverse hyperbolic sine +[ATAN] +declaration=double precision +category=Numeric/Math Functions +description=Inverse tangent, result in radians +[ATAN2] +declaration=y double precision, x double precision +category=Numeric/Math Functions +description=Inverse tangent of y/x, result in radians +[ATAN2D] +declaration=y double precision, x double precision +category=Numeric/Math Functions +description=Inverse tangent of y/x, result in degrees +[ATAND] +declaration=double precision category=Numeric/Math Functions -description=The PostgreSQL ceiling function returns the smallest integer value that is greater than or equal to a number. -[CEIL] -declaration=number +description=Inverse tangent, result in degrees +[ATANH] +declaration=double precision category=Numeric/Math Functions -description=The PostgreSQL ceil function returns the smallest integer value that is greater than or equal to a number. +description=Inverse hyperbolic tangent +[BIT_COUNT] +declaration=bit +category=Bit String Functions +description=Returns the number of bits set in the bit string (also known as\n"popcount"). +[BIT_LENGTH1] +name=BIT_LENGTH +declaration=text +category=String Functions +description=Returns number of bits in the string (8 times the octet_length). +[BIT_LENGTH2] +name=BIT_LENGTH +declaration=bytea +category=Binary String Functions +description=Returns number of bits in the binary string (8 times the octet_length). +[BIT_LENGTH3] +name=BIT_LENGTH +declaration=bit +category=Bit String Functions +description=Returns number of bits in the bit string. +[BOOL_AND] +declaration=boolean +category=Aggregate Functions +description=Returns true if all non-null input values are true, otherwise false. +[BOOL_OR] +declaration=boolean +category=Aggregate Functions +description=Returns true if any non-null input value is true, otherwise false. +[BOUND_BOX] +declaration=box, box +category=Geometric Functions +description=Computes bounding box of two boxes. +[BOX] +declaration=circle +category=Geometric Functions +description=Computes box inscribed within the circle. +[BRIN_DESUMMARIZE_RANGE] +declaration=index regclass, blockNumber bigint +category=System Administration Functions +description=Removes the BRIN index tuple that summarizes the page range covering the\ngiven table block, if there is one. +[BRIN_SUMMARIZE_NEW_VALUES] +declaration=index regclass +category=System Administration Functions +description=Scans the specified BRIN index to find page ranges in the base table that\nare not currently summarized by the index; for any such range it creates a\nnew summary index tuple by scanning those table pages. Returns the number\nof new page range summaries that were inserted into the index. +[BRIN_SUMMARIZE_RANGE] +declaration=index regclass, blockNumber bigint +category=System Administration Functions +description=Summarizes the page range covering the given block, if not already\nsummarized. This is like brin_summarize_new_values except that it only\nprocesses the page range that covers the given table block number. +[BROADCAST] +declaration=inet +category=Network Address Functions +description=Computes the broadcast address for the address's network. +[BTRIM1] +name=BTRIM +declaration=string text [, characters text ] +category=String Functions +description=Removes the longest string containing only characters in characters (a\nspace by default) from the start and end of string. +[BTRIM2] +name=BTRIM +declaration=bytes bytea, bytesremoved bytea +category=Binary String Functions +description=Removes the longest string containing only bytes appearing in bytesremoved\nfrom the start and end of bytes. +[CARDINALITY] +declaration=anyarray +category=Array Functions +description=Returns the total number of elements in the array, or 0 if the array is\nempty. +[CBRT] +declaration=double precision +category=Numeric/Math Functions +description=Cube root +[CENTER] +declaration=geometric_type +category=Geometric Functions +description=Computes center point. Available for box, circle. [CHARACTER_LENGTH] -declaration=string +declaration=text category=String Functions -description=The PostgreSQL character_length function returns the length of the specified string. -[CHAR_LENGTH] -declaration=string +description=Returns number of characters in the string. +[CHR] +declaration=integer category=String Functions -description=The PostgreSQL char_length function returns the length of the specified string. -[COUNT] -declaration=expression1, expression2, ... expression_n,aggregate_expression,tables,WHERE conditions +description=Returns the character with the given code. In UTF8 encoding the argument is\ntreated as a Unicode code point. In other multibyte encodings the argument\nmust designate an ASCII character. chr(0) is disallowed because text data\ntypes cannot store that character. +[CIRCLE] +declaration=box +category=Geometric Functions +description=Computes smallest circle enclosing box. +[CLOCK_TIMESTAMP] +declaration= +category=Date/Time Functions +description=Current date and time (changes during statement execution); see Section\n9.9.5 +[COL_DESCRIPTION] +declaration=table oid, column integer +category=Session Information Functions +description=Returns the comment for a table column, which is specified by the OID of\nits table and its column number. (obj_description cannot be used for table\ncolumns, since columns do not have OIDs of their own.) +[CONCAT] +declaration=val1 "any" [, val2 "any" [, ...] ] +category=String Functions +description=Concatenates the text representations of all the arguments. NULL arguments\nare ignored. +[CONCAT_WS] +declaration=sep text, val1 "any" [, val2 "any" [, ...] ] +category=String Functions +description=Concatenates all but the first argument, with separators. The first\nargument is used as the separator string, and should not be NULL. Other\nNULL arguments are ignored. +[CONVERT] +declaration=bytes bytea, src_encoding name, dest_encoding name +category=Binary String Functions +description=Converts a binary string representing text in encoding src_encoding to a\nbinary string in encoding dest_encoding (see Section 23.3.4 for available\nconversions). +[CONVERT_FROM] +declaration=bytes bytea, src_encoding name +category=Binary String Functions +description=Converts a binary string representing text in encoding src_encoding to text\nin the database encoding (see Section 23.3.4 for available conversions). +[CONVERT_TO] +declaration=string text, dest_encoding name +category=Binary String Functions +description=Converts a text string (in the database encoding) to a binary string\nencoded in encoding dest_encoding (see Section 23.3.4 for available\nconversions). +[COS] +declaration=double precision +category=Numeric/Math Functions +description=Cosine, argument in radians +[COSD] +declaration=double precision +category=Numeric/Math Functions +description=Cosine, argument in degrees +[COSH] +declaration=double precision category=Numeric/Math Functions -description=The PostgreSQL count function returns the count of an expression. -[CURRENT_DATE] +description=Hyperbolic cosine +[COT] +declaration=double precision +category=Numeric/Math Functions +description=Cotangent, argument in radians +[COTD] +declaration=double precision +category=Numeric/Math Functions +description=Cotangent, argument in degrees +[COUNT] +declaration=* +category=Aggregate Functions +description=Computes the number of input rows. +[CUME_DIST1] +name=CUME_DIST +declaration=args +category=Aggregate Functions +description=Computes the cumulative distribution, that is (number of rows preceding or\npeers with hypothetical row) / (total rows). The value thus ranges from 1/N\nto 1. +[CUME_DIST2] +name=CUME_DIST +declaration= +category=Window Functions +description=Returns the cumulative distribution, that is (number of partition rows\npreceding or peers with current row) / (total partition rows). The value\nthus ranges from 1/N to 1. +[CURRENT_DATABASE] +declaration= +category=Session Information Functions +description=Returns the name of the current database. (Databases are called "catalogs"\nin the SQL standard, so current_catalog is the standard's spelling.) +[CURRENT_QUERY] declaration= +category=Session Information Functions +description=Returns the text of the currently executing query, as submitted by the\nclient (which might contain more than one statement). +[CURRENT_SETTING] +declaration=setting_name text [, missing_ok boolean ] +category=System Administration Functions +description=Returns the current value of the setting setting_name. If there is no such\nsetting, current_setting throws an error unless missing_ok is supplied and\nis true (in which case NULL is returned). This function corresponds to the\nSQL command SHOW. +[CURRVAL] +declaration=regclass +category=Sequence Manipulation Functions +description=Returns the value most recently obtained by nextval for this sequence in\nthe current session. (An error is reported if nextval has never been called\nfor this sequence in this session.) Because this is returning a\nsession-local value, it gives a predictable answer whether or not other\nsessions have executed nextval since the current session did. +[DATE_ADD] +declaration=timestamp with time zone, interval [, text ] category=Date/Time Functions -description=The PostgreSQL current_date function returns the current date. -[CURRENT_TIMESTAMP] -declaration=precision +description=Add an interval to a timestamp with time zone, computing times of day and\ndaylight-savings adjustments according to the time zone named by the third\nargument, or the current TimeZone setting if that is omitted. The form with\ntwo arguments is equivalent to the timestamp with time zone + interval\noperator. +[DATE_PART] +declaration=text, timestamp category=Date/Time Functions -description=The PostgreSQL current_timestamp function returns the current date and time with the time zone. -[CURRENT_TIME] -declaration=precision +description=Get timestamp subfield (equivalent to extract); see Section 9.9.1 +[DATE_SUBTRACT] +declaration=timestamp with time zone, interval [, text ] category=Date/Time Functions -description=The PostgreSQL current_time function returns the current time with the time zone. -[DATE_PART] -declaration=date,unit +description=Subtract an interval from a timestamp with time zone, computing times of\nday and daylight-savings adjustments according to the time zone named by\nthe third argument, or the current TimeZone setting if that is omitted. The\nform with two arguments is equivalent to the timestamp with time zone -\ninterval operator. +[DATE_TRUNC] +declaration=text, timestamp category=Date/Time Functions -description=The PostgreSQL date_part function extracts parts from a date. +description=Truncate to specified precision; see Section 9.9.2 +[DECODE] +declaration=string text, format text +category=Binary String Functions +description=Decodes binary data from a textual representation; supported format values\nare the same as for encode. +[DEGREES] +declaration=double precision +category=Numeric/Math Functions +description=Converts radians to degrees +[DENSE_RANK1] +name=DENSE_RANK +declaration=args +category=Aggregate Functions +description=Computes the rank of the hypothetical row, without gaps; this function\neffectively counts peer groups. +[DENSE_RANK2] +name=DENSE_RANK +declaration= +category=Window Functions +description=Returns the rank of the current row, without gaps; this function\neffectively counts peer groups. +[DIAGONAL] +declaration=box +category=Geometric Functions +description=Extracts box's diagonal as a line segment (same as lseg(box)). +[DIAMETER] +declaration=circle +category=Geometric Functions +description=Computes diameter of circle. [DIV] -declaration=n,m +declaration=y numeric, x numeric +category=Numeric/Math Functions +description=Integer quotient of y/x (truncates towards zero) +[ENCODE] +declaration=bytes bytea, format text +category=Binary String Functions +description=Encodes binary data into a textual representation; supported format values\nare: base64, escape, hex. +[ENUM_FIRST] +declaration=anyenum +category=Enum Support Functions +description=Returns the first value of the input enum type. +[ENUM_LAST] +declaration=anyenum +category=Enum Support Functions +description=Returns the last value of the input enum type. +[ENUM_RANGE] +declaration=anyenum +category=Enum Support Functions +description=Returns all values of the input enum type in an ordered array. +[ERF] +declaration=double precision category=Numeric/Math Functions -description=The PostgreSQL div function is used for integer division where n is divided by m and an integer value is returned. -[EXP] -declaration=number +description=Error function +[ERFC] +declaration=double precision category=Numeric/Math Functions -description=The PostgreSQL exp function returns e raised to the power of number (or enumber). +description=Complementary error function (1 - erf(x), without loss of precision for\nlarge inputs) +[EVERY] +declaration=boolean +category=Aggregate Functions +description=This is the SQL standard's equivalent to bool_and. [EXTRACT] -declaration=date,unit +declaration=field from timestamp category=Date/Time Functions -description=The PostgreSQL extract function extracts parts from a date. -[FLOOR] -declaration=number +description=Get timestamp subfield; see Section 9.9.1 +[FACTORIAL] +declaration=bigint category=Numeric/Math Functions -description=The PostgreSQL floor function returns the largest integer value that is equal to or less than a number. -[INITCAP] -declaration=string +description=Factorial +[FAMILY] +declaration=inet +category=Network Address Functions +description=Returns the address's family: 4 for IPv4, 6 for IPv6. +[FIRST_VALUE] +declaration=value anyelement +category=Window Functions +description=Returns value evaluated at the row that is the first row of the window\nframe. +[FORMAT] +declaration=formatstr text [, formatarg "any" [, ...] ] category=String Functions -description=The PostgreSQL initcap function converts the first letter of each word to uppercase and all other letters are converted to lowercase. -[LENGTH] -declaration=string +description=Formats arguments according to a format string; see Section 9.4.1. This\nfunction is similar to the C function sprintf. +[FORMAT_TYPE] +declaration=type oid, typemod integer +category=Session Information Functions +description=Returns the SQL name for a data type that is identified by its type OID and\npossibly a type modifier. Pass NULL for the type modifier if no specific\nmodifier is known. +[GCD] +declaration=numeric_type, numeric_type +category=Numeric/Math Functions +description=Greatest common divisor (the largest positive number that divides both\ninputs with no remainder); returns 0 if both inputs are zero; available for\ninteger, bigint, and numeric +[GET_BIT1] +name=GET_BIT +declaration=bytes bytea, n bigint +category=Binary String Functions +description=Extracts n'th bit from binary string. +[GET_BIT2] +name=GET_BIT +declaration=bits bit, n integer +category=Bit String Functions +description=Extracts n'th bit from bit string; the first (leftmost) bit is bit 0. +[GET_BYTE] +declaration=bytes bytea, n integer +category=Binary String Functions +description=Extracts n'th byte from binary string. +[GET_CURRENT_TS_CONFIG] +declaration= +category=Text Search Functions +description=Returns the OID of the current default text search configuration (as set by\ndefault_text_search_config). +[GIN_CLEAN_PENDING_LIST] +declaration=index regclass +category=System Administration Functions +description=Cleans up the "pending" list of the specified GIN index by moving entries\nin it, in bulk, to the main GIN data structure. Returns the number of pages\nremoved from the pending list. If the argument is a GIN index built with\nthe fastupdate option disabled, no cleanup happens and the result is zero,\nbecause the index doesn't have a pending list. See Section 64.4.4.1 and\nSection 64.4.5 for details about the pending list and fastupdate option. +[GROUPING] +declaration=group_by_expression(s +category=Aggregate Functions +description=Returns a bit mask indicating which GROUP BY expressions are not included\nin the current grouping set. Bits are assigned with the rightmost argument\ncorresponding to the least-significant bit; each bit is 0 if the\ncorresponding expression is included in the grouping criteria of the\ngrouping set generating the current result row, and 1 if it is not\nincluded. +[HAS_ANY_COLUMN_PRIVILEGE] +declaration=[ user name or oid, ] table text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for any column of table? This succeeds either if\nthe privilege is held for the whole table, or if there is a column-level\ngrant of the privilege for at least one column. Allowable privilege types\nare SELECT, INSERT, UPDATE, and REFERENCES. +[HAS_COLUMN_PRIVILEGE] +declaration=[ user name or oid, ] table text or oid, column text or smallint, privilege text +category=Session Information Functions +description=Does user have privilege for the specified table column? This succeeds\neither if the privilege is held for the whole table, or if there is a\ncolumn-level grant of the privilege for the column. The column can be\nspecified by name or by attribute number (pg_attribute.attnum). Allowable\nprivilege types are SELECT, INSERT, UPDATE, and REFERENCES. +[HAS_DATABASE_PRIVILEGE] +declaration=[ user name or oid, ] database text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for database? Allowable privilege types are\nCREATE, CONNECT, TEMPORARY, and TEMP (which is equivalent to TEMPORARY). +[HAS_FOREIGN_DATA_WRAPPER_PRIVILEGE] +declaration=[ user name or oid, ] fdw text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for foreign-data wrapper? The only allowable\nprivilege type is USAGE. +[HAS_FUNCTION_PRIVILEGE] +declaration=[ user name or oid, ] function text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for function? The only allowable privilege type is\nEXECUTE. +[HAS_LANGUAGE_PRIVILEGE] +declaration=[ user name or oid, ] language text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for language? The only allowable privilege type is\nUSAGE. +[HAS_PARAMETER_PRIVILEGE] +declaration=[ user name or oid, ] parameter text, privilege text +category=Session Information Functions +description=Does user have privilege for configuration parameter? The parameter name is\ncase-insensitive. Allowable privilege types are SET and ALTER SYSTEM. +[HAS_SCHEMA_PRIVILEGE] +declaration=[ user name or oid, ] schema text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for schema? Allowable privilege types are CREATE\nand USAGE. +[HAS_SEQUENCE_PRIVILEGE] +declaration=[ user name or oid, ] sequence text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for sequence? Allowable privilege types are USAGE,\nSELECT, and UPDATE. +[HAS_SERVER_PRIVILEGE] +declaration=[ user name or oid, ] server text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for foreign server? The only allowable privilege\ntype is USAGE. +[HAS_TABLESPACE_PRIVILEGE] +declaration=[ user name or oid, ] tablespace text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for tablespace? The only allowable privilege type\nis CREATE. +[HAS_TABLE_PRIVILEGE] +declaration=[ user name or oid, ] table text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for table? Allowable privilege types are SELECT,\nINSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, and MAINTAIN. +[HAS_TYPE_PRIVILEGE] +declaration=[ user name or oid, ] type text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for data type? The only allowable privilege type\nis USAGE. When specifying a type by name rather than by OID, the allowed\ninput is the same as for the regtype data type (see Section 8.19). +[HEIGHT] +declaration=box +category=Geometric Functions +description=Computes vertical size of box. +[HOST] +declaration=inet +category=Network Address Functions +description=Returns the IP address as text, ignoring the netmask. +[HOSTMASK] +declaration=inet +category=Network Address Functions +description=Computes the host mask for the address's network. +[ICU_UNICODE_VERSION] +declaration= +category=Session Information Functions +description=Returns a string representing the version of Unicode used by ICU, if the\nserver was built with ICU support; otherwise returns NULL +[INET_CLIENT_ADDR] +declaration= +category=Session Information Functions +description=Returns the IP address of the current client, or NULL if the current\nconnection is via a Unix-domain socket. +[INET_CLIENT_PORT] +declaration= +category=Session Information Functions +description=Returns the IP port number of the current client, or NULL if the current\nconnection is via a Unix-domain socket. +[INET_MERGE] +declaration=inet, inet +category=Network Address Functions +description=Computes the smallest network that includes both of the given networks. +[INET_SAME_FAMILY] +declaration=inet, inet +category=Network Address Functions +description=Tests whether the addresses belong to the same IP family. +[INET_SERVER_ADDR] +declaration= +category=Session Information Functions +description=Returns the IP address on which the server accepted the current connection,\nor NULL if the current connection is via a Unix-domain socket. +[INET_SERVER_PORT] +declaration= +category=Session Information Functions +description=Returns the IP port number on which the server accepted the current\nconnection, or NULL if the current connection is via a Unix-domain socket. +[INITCAP] +declaration=text category=String Functions -description=The PostgreSQL length function returns the length of the specified string, expressed as the number of characters. -[LOCALTIMESTAMP] -declaration=precision +description=Converts the first letter of each word to upper case and the rest to lower\ncase. Words are sequences of alphanumeric characters separated by\nnon-alphanumeric characters. +[ISCLOSED] +declaration=path +category=Geometric Functions +description=Is path closed? +[ISEMPTY1] +name=ISEMPTY +declaration=anyrange +category=Range Functions +description=Is the range empty? +[ISEMPTY2] +name=ISEMPTY +declaration=anymultirange +category=Range Functions +description=Is the multirange empty? +[ISFINITE] +declaration=date +category=Date/Time Functions +description=Test for finite date (not +/-infinity) +[ISOPEN] +declaration=path +category=Geometric Functions +description=Is path open? +[JSON] +declaration=expression [ FORMAT JSON [ ENCODING UTF8 ]] [ { WITH | WITHOUT } UNIQUE [ KEYS ]] +category=JSON Functions +description=Converts a given expression specified as text or bytea string (in UTF8\nencoding) into a JSON value. If expression is NULL, an SQL null value is\nreturned. If WITH UNIQUE is specified, the expression must not contain any\nduplicate object keys. +[JSONB_AGG] +declaration=anyelement ORDER BY input_sort_columns +category=Aggregate Functions +description=Collects all the input values, including nulls, into a JSON array. Values\nare converted to JSON as per to_json or to_jsonb. +[JSONB_AGG_STRICT] +declaration=anyelement +category=Aggregate Functions +description=Collects all the input values, skipping nulls, into a JSON array. Values\nare converted to JSON as per to_json or to_jsonb. +[JSONB_ARRAY_ELEMENTS] +declaration=jsonb +category=JSON Functions +description=Expands the top-level JSON array into a set of JSON values. +[JSONB_ARRAY_ELEMENTS_TEXT] +declaration=jsonb +category=JSON Functions +description=Expands the top-level JSON array into a set of text values. +[JSONB_ARRAY_LENGTH] +declaration=jsonb +category=JSON Functions +description=Returns the number of elements in the top-level JSON array. +[JSONB_BUILD_ARRAY] +declaration=VARIADIC "any" +category=JSON Functions +description=Builds a possibly-heterogeneously-typed JSON array out of a variadic\nargument list. Each argument is converted as per to_json or to_jsonb. +[JSONB_BUILD_OBJECT] +declaration=VARIADIC "any" +category=JSON Functions +description=Builds a JSON object out of a variadic argument list. By convention, the\nargument list consists of alternating keys and values. Key arguments are\ncoerced to text; value arguments are converted as per to_json or to_jsonb. +[JSONB_EACH] +declaration=jsonb +category=JSON Functions +description=Expands the top-level JSON object into a set of key/value pairs. +[JSONB_EACH_TEXT] +declaration=jsonb +category=JSON Functions +description=Expands the top-level JSON object into a set of key/value pairs. The\nreturned values will be of type text. +[JSONB_EXTRACT_PATH] +declaration=from_json jsonb, VARIADIC path_elems text[] +category=JSON Functions +description=Extracts JSON sub-object at the specified path. (This is functionally\nequivalent to the #> operator, but writing the path out as a variadic\nlist can be more convenient in some cases.) +[JSONB_EXTRACT_PATH_TEXT] +declaration=from_json jsonb, VARIADIC path_elems text[] +category=JSON Functions +description=Extracts JSON sub-object at the specified path as text. (This is\nfunctionally equivalent to the #>> operator.) +[JSONB_INSERT] +declaration=target jsonb, path text[], new_value jsonb [, insert_after boolean ] +category=JSON Functions +description=Returns target with new_value inserted. If the item designated by the path\nis an array element, new_value will be inserted before that item if\ninsert_after is false (which is the default), or after it if insert_after\nis true. If the item designated by the path is an object field, new_value\nwill be inserted only if the object does not already contain that key. All\nearlier steps in the path must exist, or the target is returned unchanged.\nAs with the path oriented operators, negative integers that appear in the\npath count from the end of JSON arrays. If the last path step is an array\nindex that is out of range, the new value is added at the beginning of the\narray if the index is negative, or at the end of the array if it is\npositive. +[JSONB_OBJECT] +declaration=text[] +category=JSON Functions +description=Builds a JSON object out of a text array. The array must have either\nexactly one dimension with an even number of members, in which case they\nare taken as alternating key/value pairs, or two dimensions such that each\ninner array has exactly two elements, which are taken as a key/value pair.\nAll values are converted to JSON strings. +[JSONB_OBJECT_AGG] +declaration=key "any", value "any" ORDER BY input_sort_columns +category=Aggregate Functions +description=Collects all the key/value pairs into a JSON object. Key arguments are\ncoerced to text; value arguments are converted as per to_json or to_jsonb.\nValues can be null, but keys cannot. +[JSONB_OBJECT_AGG_STRICT] +declaration=key "any", value "any" +category=Aggregate Functions +description=Collects all the key/value pairs into a JSON object. Key arguments are\ncoerced to text; value arguments are converted as per to_json or to_jsonb.\nThe key can not be null. If the value is null then the entry is skipped, +[JSONB_OBJECT_AGG_UNIQUE] +declaration=key "any", value "any" +category=Aggregate Functions +description=Collects all the key/value pairs into a JSON object. Key arguments are\ncoerced to text; value arguments are converted as per to_json or to_jsonb.\nValues can be null, but keys cannot. If there is a duplicate key an error\nis thrown. +[JSONB_OBJECT_AGG_UNIQUE_STRICT] +declaration=key "any", value "any" +category=Aggregate Functions +description=Collects all the key/value pairs into a JSON object. Key arguments are\ncoerced to text; value arguments are converted as per to_json or to_jsonb.\nThe key can not be null. If the value is null then the entry is skipped. If\nthere is a duplicate key an error is thrown. +[JSONB_OBJECT_KEYS] +declaration=jsonb +category=JSON Functions +description=Returns the set of keys in the top-level JSON object. +[JSONB_PATH_EXISTS] +declaration=target jsonb, path jsonpath [, vars jsonb [, silent boolean ]] +category=JSON Functions +description=Checks whether the JSON path returns any item for the specified JSON value.\n(This is useful only with SQL-standard JSON path expressions, not predicate\ncheck expressions, since those always return a value.) If the vars argument\nis specified, it must be a JSON object, and its fields provide named values\nto be substituted into the jsonpath expression. If the silent argument is\nspecified and is true, the function suppresses the same errors as the @?\nand @@ operators do. +[JSONB_PATH_MATCH] +declaration=target jsonb, path jsonpath [, vars jsonb [, silent boolean ]] +category=JSON Functions +description=Returns the result of a JSON path predicate check for the specified JSON\nvalue. (This is useful only with predicate check expressions, not\nSQL-standard JSON path expressions, since it will either fail or return\nNULL if the path result is not a single boolean value.) The optional vars\nand silent arguments act the same as for jsonb_path_exists. +[JSONB_PATH_QUERY] +declaration=target jsonb, path jsonpath [, vars jsonb [, silent boolean ]] +category=JSON Functions +description=Returns all JSON items returned by the JSON path for the specified JSON\nvalue. For SQL-standard JSON path expressions it returns the JSON values\nselected from target. For predicate check expressions it returns the result\nof the predicate check: true, false, or null. The optional vars and silent\narguments act the same as for jsonb_path_exists. +[JSONB_PATH_QUERY_ARRAY] +declaration=target jsonb, path jsonpath [, vars jsonb [, silent boolean ]] +category=JSON Functions +description=Returns all JSON items returned by the JSON path for the specified JSON\nvalue, as a JSON array. The parameters are the same as for\njsonb_path_query. +[JSONB_PATH_QUERY_FIRST] +declaration=target jsonb, path jsonpath [, vars jsonb [, silent boolean ]] +category=JSON Functions +description=Returns the first JSON item returned by the JSON path for the specified\nJSON value, or NULL if there are no results. The parameters are the same as\nfor jsonb_path_query. +[JSONB_PATH_QUERY_FIRST_TZ] +declaration=target jsonb, path jsonpath [, vars jsonb [, silent boolean ]] +category=JSON Functions +description=These functions act like their counterparts described above without the _tz\nsuffix, except that these functions support comparisons of date/time values\nthat require timezone-aware conversions. The example below requires\ninterpretation of the date-only value 2015-08-02 as a timestamp with time\nzone, so the result depends on the current TimeZone setting. Due to this\ndependency, these functions are marked as stable, which means these\nfunctions cannot be used in indexes. Their counterparts are immutable, and\nso can be used in indexes; but they will throw errors if asked to make such\ncomparisons. +[JSONB_POPULATE_RECORD] +declaration=base anyelement, from_json jsonb +category=JSON Functions +description=Expands the top-level JSON object to a row having the composite type of the\nbase argument. The JSON object is scanned for fields whose names match\ncolumn names of the output row type, and their values are inserted into\nthose columns of the output. (Fields that do not correspond to any output\ncolumn name are ignored.) In typical use, the value of base is just NULL,\nwhich means that any output columns that do not match any object field will\nbe filled with nulls. However, if base isn't NULL then the values it\ncontains will be used for unmatched columns. +[JSONB_POPULATE_RECORDSET] +declaration=base anyelement, from_json jsonb +category=JSON Functions +description=Expands the top-level JSON array of objects to a set of rows having the\ncomposite type of the base argument. Each element of the JSON array is\nprocessed as described above for json[b]_populate_record. +[JSONB_POPULATE_RECORD_VALID] +declaration=base anyelement, from_json json +category=JSON Functions +description=Function for testing jsonb_populate_record. Returns true if the input\njsonb_populate_record would finish without an error for the given input\nJSON object; that is, it's valid input, false otherwise. +[JSONB_PRETTY] +declaration=jsonb +category=JSON Functions +description=Converts the given JSON value to pretty-printed, indented text. +[JSONB_SET] +declaration=target jsonb, path text[], new_value jsonb [, create_if_missing boolean ] +category=JSON Functions +description=Returns target with the item designated by path replaced by new_value, or\nwith new_value added if create_if_missing is true (which is the default)\nand the item designated by path does not exist. All earlier steps in the\npath must exist, or the target is returned unchanged. As with the path\noriented operators, negative integers that appear in the path count from\nthe end of JSON arrays. If the last path step is an array index that is out\nof range, and create_if_missing is true, the new value is added at the\nbeginning of the array if the index is negative, or at the end of the array\nif it is positive. +[JSONB_SET_LAX] +declaration=target jsonb, path text[], new_value jsonb [, create_if_missing boolean [, null_value_treatment text ]] +category=JSON Functions +description=If new_value is not NULL, behaves identically to jsonb_set. Otherwise\nbehaves according to the value of null_value_treatment which must be one of\n'raise_exception', 'use_json_null', 'delete_key', or 'return_target'. The\ndefault is 'use_json_null'. +[JSONB_STRIP_NULLS] +declaration=jsonb +category=JSON Functions +description=Deletes all object fields that have null values from the given JSON value,\nrecursively. Null values that are not object fields are untouched. +[JSONB_TO_RECORD] +declaration=jsonb +category=JSON Functions +description=Expands the top-level JSON object to a row having the composite type\ndefined by an AS clause. (As with all functions returning record, the\ncalling query must explicitly define the structure of the record with an AS\nclause.) The output record is filled from fields of the JSON object, in the\nsame way as described above for json[b]_populate_record. Since there is no\ninput record value, unmatched columns are always filled with nulls. +[JSONB_TO_RECORDSET] +declaration=jsonb +category=JSON Functions +description=Expands the top-level JSON array of objects to a set of rows having the\ncomposite type defined by an AS clause. (As with all functions returning\nrecord, the calling query must explicitly define the structure of the\nrecord with an AS clause.) Each element of the JSON array is processed as\ndescribed above for json[b]_populate_record. +[JSONB_TO_TSVECTOR] +declaration=[ config regconfig, ] document jsonb, filter jsonb +category=Text Search Functions +description=Selects each item in the JSON document that is requested by the filter and\nconverts each one to a tsvector, normalizing words according to the\nspecified or default configuration. The results are then concatenated in\ndocument order to produce the output. Position information is generated as\nthough one stopword exists between each pair of selected items. (Beware\nthat "document order" of the fields of a JSON object is\nimplementation-dependent when the input is jsonb.) The filter must be a\njsonb array containing zero or more of these keywords: "string" (to include\nall string values), "numeric" (to include all numeric values), "boolean"\n(to include all boolean values), "key" (to include all keys), or "all" (to\ninclude all the above). As a special case, the filter can also be a simple\nJSON value that is one of these keywords. +[JSONB_TYPEOF] +declaration=jsonb +category=JSON Functions +description=Returns the type of the top-level JSON value as a text string. Possible\ntypes are object, array, string, number, boolean, and null. (The null\nresult should not be confused with an SQL NULL; see the examples.) +[JSON_ARRAYAGG] +declaration=[ value_expression ] [ ORDER BY sort_expression ] [ { NULL | ABSENT } ON NULL ] [ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] ] +category=Aggregate Functions +description=Behaves in the same way as json_array but as an aggregate function so it\nonly takes one value_expression parameter. If ABSENT ON NULL is specified,\nany NULL values are omitted. If ORDER BY is specified, the elements will\nappear in the array in that order rather than in the input order. +[JSON_OBJECT] +declaration=[ { key_expression { VALUE | ':' } value_expression [ FORMAT JSON [ ENCODING UTF8 ] ] }[, ...] ] [ { NULL | ABSENT } ON NULL ] [ { WITH | WITHOUT } UNIQUE [ KEYS ] ] [ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] ] +category=JSON Functions +description=Constructs a JSON object of all the key/value pairs given, or an empty\nobject if none are given. key_expression is a scalar expression defining\nthe JSON key, which is converted to the text type. It cannot be NULL nor\ncan it belong to a type that has a cast to the json type. If WITH UNIQUE\nKEYS is specified, there must not be any duplicate key_expression. Any pair\nfor which the value_expression evaluates to NULL is omitted from the output\nif ABSENT ON NULL is specified; if NULL ON NULL is specified or the clause\nomitted, the key is included with value NULL. +[JSON_OBJECTAGG] +declaration=[ { key_expression { VALUE | ':' } value_expression } ] [ { NULL | ABSENT } ON NULL ] [ { WITH | WITHOUT } UNIQUE [ KEYS ] ] [ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] ] +category=Aggregate Functions +description=Behaves like json_object, but as an aggregate function, so it only takes\none key_expression and one value_expression parameter. +[JSON_SCALAR] +declaration=expression +category=JSON Functions +description=Converts a given SQL scalar value into a JSON scalar value. If the input is\nNULL, an SQL null is returned. If the input is number or a boolean value, a\ncorresponding JSON number or boolean value is returned. For any other\nvalue, a JSON string is returned. +[JUSTIFY_DAYS] +declaration=interval category=Date/Time Functions -description=The PostgreSQL localtimestamp function returns the current date and time. -[LOCALTIME] -declaration=precision +description=Adjust interval, converting 30-day time periods to months +[JUSTIFY_HOURS] +declaration=interval category=Date/Time Functions -description=The PostgreSQL localtime function returns the current time. -[LOWER] -declaration=string +description=Adjust interval, converting 24-hour time periods to days +[JUSTIFY_INTERVAL] +declaration=interval +category=Date/Time Functions +description=Adjust interval using justify_days and justify_hours, with additional sign\nadjustments +[LAG] +declaration=value anycompatible [, offset integer [, default anycompatible ]] +category=Window Functions +description=Returns value evaluated at the row that is offset rows before the current\nrow within the partition; if there is no such row, instead returns default\n(which must be of a type compatible with value). Both offset and default\nare evaluated with respect to the current row. If omitted, offset defaults\nto 1 and default to NULL. +[LASTVAL] +declaration= +category=Sequence Manipulation Functions +description=Returns the value most recently returned by nextval in the current session.\nThis function is identical to currval, except that instead of taking the\nsequence name as an argument it refers to whichever sequence nextval was\nmost recently applied to in the current session. It is an error to call\nlastval if nextval has not yet been called in the current session. +[LAST_VALUE] +declaration=value anyelement +category=Window Functions +description=Returns value evaluated at the row that is the last row of the window\nframe. +[LCM] +declaration=numeric_type, numeric_type +category=Numeric/Math Functions +description=Least common multiple (the smallest strictly positive number that is an\nintegral multiple of both inputs); returns 0 if either input is zero;\navailable for integer, bigint, and numeric +[LEAD] +declaration=value anycompatible [, offset integer [, default anycompatible ]] +category=Window Functions +description=Returns value evaluated at the row that is offset rows after the current\nrow within the partition; if there is no such row, instead returns default\n(which must be of a type compatible with value). Both offset and default\nare evaluated with respect to the current row. If omitted, offset defaults\nto 1 and default to NULL. +[LEFT] +declaration=string text, n integer +category=String Functions +description=Returns first n characters in the string, or when n is negative, returns\nall but last |n| characters. +[LENGTH1] +name=LENGTH +declaration=text +category=String Functions +description=Returns the number of characters in the string. +[LENGTH2] +name=LENGTH +declaration=geometric_type +category=Geometric Functions +description=Computes the total length. Available for lseg, path. +[LENGTH3] +name=LENGTH +declaration=tsvector +category=Text Search Functions +description=Returns the number of lexemes in the tsvector. +[LINE] +declaration=point, point +category=Geometric Functions +description=Converts two points to the line through them. +[LOWER1] +name=LOWER +declaration=text category=String Functions -description=The PostgreSQL lower function converts all characters in the specified string to lowercase. +description=Converts the string to all lower case, according to the rules of the\ndatabase's locale. +[LOWER2] +name=LOWER +declaration=anyrange +category=Range Functions +description=Extracts the lower bound of the range (NULL if the range is empty or has no\nlower bound). +[LOWER3] +name=LOWER +declaration=anymultirange +category=Range Functions +description=Extracts the lower bound of the multirange (NULL if the multirange is empty\nhas no lower bound). +[LOWER_INC1] +name=LOWER_INC +declaration=anyrange +category=Range Functions +description=Is the range's lower bound inclusive? +[LOWER_INC2] +name=LOWER_INC +declaration=anymultirange +category=Range Functions +description=Is the multirange's lower bound inclusive? +[LOWER_INF1] +name=LOWER_INF +declaration=anyrange +category=Range Functions +description=Does the range have no lower bound? (A lower bound of -Infinity returns\nfalse.) +[LOWER_INF2] +name=LOWER_INF +declaration=anymultirange +category=Range Functions +description=Does the multirange have no lower bound? (A lower bound of -Infinity\nreturns false.) [LPAD] -declaration=string,length,pad_string +declaration=string text, length integer [, fill text ] category=String Functions -description=The PostgreSQL lpad function returns a string that is left-padded with a specified string to a certain length. -[LTRIM] -declaration=string,trim_character +description=Extends the string to length length by prepending the characters fill (a\nspace by default). If the string is already longer than length then it is\ntruncated (on the right). +[LSEG] +declaration=box +category=Geometric Functions +description=Extracts box's diagonal as a line segment. +[LTRIM1] +name=LTRIM +declaration=string text [, characters text ] category=String Functions -description=The PostgreSQL ltrim function removes all specified characters from the left-hand side of a string. +description=Removes the longest string containing only characters in characters (a\nspace by default) from the start of string. +[LTRIM2] +name=LTRIM +declaration=bytes bytea, bytesremoved bytea +category=Binary String Functions +description=Removes the longest string containing only bytes appearing in bytesremoved\nfrom the start of bytes. +[MACADDR8_SET7BIT] +declaration=macaddr8 +category=Network Address Functions +description=Sets the 7th bit of the address to one, creating what is known as modified\nEUI-64, for inclusion in an IPv6 address. +[MAKEACLITEM] +declaration=grantee oid, grantor oid, privileges text, is_grantable boolean +category=Session Information Functions +description=Constructs an aclitem with the given properties. privileges is a\ncomma-separated list of privilege names such as SELECT, INSERT, etc, all of\nwhich are set in the result. (Case of the privilege string is not\nsignificant, and extra whitespace is allowed between but not within\nprivilege names.) +[MAKE_DATE] +declaration=year int, month int, day int +category=Date/Time Functions +description=Create date from year, month and day fields (negative years signify BC) +[MAKE_INTERVAL] +declaration=[ years int [, months int [, weeks int [, days int [, hours int [, mins int [, secs double precision ]]]]]]] +category=Date/Time Functions +description=Create interval from years, months, weeks, days, hours, minutes and seconds\nfields, each of which can default to zero +[MAKE_TIME] +declaration=hour int, min int, sec double precision +category=Date/Time Functions +description=Create time from hour, minute and seconds fields +[MAKE_TIMESTAMP] +declaration=year int, month int, day int, hour int, min int, sec double precision +category=Date/Time Functions +description=Create timestamp from year, month, day, hour, minute and seconds fields\n(negative years signify BC) +[MAKE_TIMESTAMPTZ] +declaration=year int, month int, day int, hour int, min int, sec double precision [, timezone text ] +category=Date/Time Functions +description=Create timestamp with time zone from year, month, day, hour, minute and\nseconds fields (negative years signify BC). If timezone is not specified,\nthe current time zone is used; the examples assume the session time zone is\nEurope/London +[MASKLEN] +declaration=inet +category=Network Address Functions +description=Returns the netmask length in bits. [MAX] -declaration=expression1, expression2, ... expression_n,aggregate_expression,tables,WHERE conditions -category=Numeric/Math Functions -description=The PostgreSQL max function returns the maximum value of an expression. +declaration=see text +category=Aggregate Functions +description=Computes the maximum of the non-null input values. Available for any\nnumeric, string, date/time, or enum type, as well as inet, interval, money,\noid, pg_lsn, tid, xid8, and arrays of any of these types. +[MD51] +name=MD5 +declaration=text +category=String Functions +description=Computes the MD5 hash of the argument, with the result written in\nhexadecimal. +[MD52] +name=MD5 +declaration=bytea +category=Binary String Functions +description=Computes the MD5 hash of the binary string, with the result written in\nhexadecimal. +[MERGE_ACTION] +declaration= +category=Merge Support Functions +description=Returns the merge action command executed for the current row. This will be\n'INSERT', 'UPDATE', or 'DELETE'. [MIN] -declaration=expression1, expression2, ... expression_n,aggregate_expression,tables,WHERE conditions +declaration=see text +category=Aggregate Functions +description=Computes the minimum of the non-null input values. Available for any\nnumeric, string, date/time, or enum type, as well as inet, interval, money,\noid, pg_lsn, tid, xid8, and arrays of any of these types. +[MIN_SCALE] +declaration=numeric category=Numeric/Math Functions -description=The PostgreSQL min function returns the minimum value of an expression. +description=Minimum scale (number of fractional decimal digits) needed to represent the\nsupplied value precisely [MOD] -declaration=n,m +declaration=y numeric_type, x numeric_type category=Numeric/Math Functions -description=The PostgreSQL mod function returns the remainder of n divided by m. +description=Remainder of y/x; available for smallint, integer, bigint, and numeric +[MODE] +declaration= +category=Aggregate Functions +description=Computes the mode, the most frequent value of the aggregated argument\n(arbitrarily choosing the first one if there are multiple equally-frequent\nvalues). The aggregated argument must be of a sortable type. +[MULTIRANGE] +declaration=anyrange +category=Range Functions +description=Returns a multirange containing just the given range. +[MXID_AGE] +declaration=xid +category=Session Information Functions +description=Returns the number of multixacts IDs between the supplied multixact ID and\nthe current multixacts counter. +[NETMASK] +declaration=inet +category=Network Address Functions +description=Computes the network mask for the address's network. +[NETWORK] +declaration=inet +category=Network Address Functions +description=Returns the network part of the address, zeroing out whatever is to the\nright of the netmask. (This is equivalent to casting the value to cidr.) +[NEXTVAL] +declaration=regclass +category=Sequence Manipulation Functions +description=Advances the sequence object to its next value and returns that value. This\nis done atomically: even if multiple sessions execute nextval concurrently,\neach will safely receive a distinct sequence value. If the sequence object\nhas been created with default parameters, successive nextval calls will\nreturn successive values beginning with 1. Other behaviors can be obtained\nby using appropriate parameters in the CREATE SEQUENCE command. [NOW] -declaration=precision +declaration= category=Date/Time Functions -description=The PostgreSQL now function returns the current date and time with the time zone. -[POSITION] -declaration=substring,string +description=Current date and time (start of current transaction); see Section 9.9.5 +[NPOINTS] +declaration=geometric_type +category=Geometric Functions +description=Returns the number of points. Available for path, polygon. +[NTH_VALUE] +declaration=value anyelement, n integer +category=Window Functions +description=Returns value evaluated at the row that is the n'th row of the window frame\n(counting from 1); returns NULL if there is no such row. +[NTILE] +declaration=num_buckets integer +category=Window Functions +description=Returns an integer ranging from 1 to the argument value, dividing the\npartition as equally as possible. +[NUMNODE] +declaration=tsquery +category=Text Search Functions +description=Returns the number of lexemes plus operators in the tsquery. +[OBJ_DESCRIPTION] +declaration=object oid, catalog name +category=Session Information Functions +description=Returns the comment for a database object specified by its OID and the name\nof the containing system catalog. For example, obj_description(123456,\n'pg_class') would retrieve the comment for the table with OID 123456. +[OCTET_LENGTH1] +name=OCTET_LENGTH +declaration=text +category=String Functions +description=Returns number of bytes in the string. +[OCTET_LENGTH2] +name=OCTET_LENGTH +declaration=character +category=String Functions +description=Returns number of bytes in the string. Since this version of the function\naccepts type character directly, it will not strip trailing spaces. +[OCTET_LENGTH3] +name=OCTET_LENGTH +declaration=bytea +category=Binary String Functions +description=Returns number of bytes in the binary string. +[OCTET_LENGTH4] +name=OCTET_LENGTH +declaration=bit +category=Bit String Functions +description=Returns number of bytes in the bit string. +[OVERLAY1] +name=OVERLAY +declaration=string text PLACING newsubstring text FROM start integer [ FOR count integer ] +category=String Functions +description=Replaces the substring of string that starts at the start'th character and\nextends for count characters with newsubstring. If count is omitted, it\ndefaults to the length of newsubstring. +[OVERLAY2] +name=OVERLAY +declaration=bytes bytea PLACING newsubstring bytea FROM start integer [ FOR count integer ] +category=Binary String Functions +description=Replaces the substring of bytes that starts at the start'th byte and\nextends for count bytes with newsubstring. If count is omitted, it defaults\nto the length of newsubstring. +[OVERLAY3] +name=OVERLAY +declaration=bits bit PLACING newsubstring bit FROM start integer [ FOR count integer ] +category=Bit String Functions +description=Replaces the substring of bits that starts at the start'th bit and extends\nfor count bits with newsubstring. If count is omitted, it defaults to the\nlength of newsubstring. +[PARSE_IDENT] +declaration=qualified_identifier text [, strict_mode boolean DEFAULT true ] +category=String Functions +description=Splits qualified_identifier into an array of identifiers, removing any\nquoting of individual identifiers. By default, extra characters after the\nlast identifier are considered an error; but if the second parameter is\nfalse, then such extra characters are ignored. (This behavior is useful for\nparsing names for objects like functions.) Note that this function does not\ntruncate over-length identifiers. If you want truncation you can cast the\nresult to name[]. +[PATH] +declaration=polygon +category=Geometric Functions +description=Converts polygon to a closed path with the same list of points. +[PCLOSE] +declaration=path +category=Geometric Functions +description=Converts path to closed form. +[PERCENTILE_DISC] +declaration=fraction double precision +category=Aggregate Functions +description=Computes the discrete percentile, the first value within the ordered set of\naggregated argument values whose position in the ordering equals or exceeds\nthe specified fraction. The aggregated argument must be of a sortable type. +[PERCENT_RANK1] +name=PERCENT_RANK +declaration=args +category=Aggregate Functions +description=Computes the relative rank of the hypothetical row, that is (rank - 1) /\n(total rows - 1). The value thus ranges from 0 to 1 inclusive. +[PERCENT_RANK2] +name=PERCENT_RANK +declaration= +category=Window Functions +description=Returns the relative rank of the current row, that is (rank - 1) / (total\npartition rows - 1). The value thus ranges from 0 to 1 inclusive. +[PG_ADVISORY_UNLOCK_ALL] +declaration= +category=System Administration Functions +description=Releases all session-level advisory locks held by the current session.\n(This function is implicitly invoked at session end, even if the client\ndisconnects ungracefully.) +[PG_AVAILABLE_WAL_SUMMARIES] +declaration= +category=Session Information Functions +description=Returns information about the WAL summary files present in the data\ndirectory, under pg_wal/summaries. One row will be returned per WAL summary\nfile. Each file summarizes WAL on the indicated TLI within the indicated\nLSN range. This function might be useful to determine whether enough WAL\nsummaries are present on the server to take an incremental backup based on\nsome prior backup whose start LSN is known. +[PG_BACKEND_PID] +declaration= +category=Session Information Functions +description=Returns the process ID of the server process attached to the current\nsession. +[PG_BACKUP_START] +declaration=label text [, fast boolean ] +category=System Administration Functions +description=Prepares the server to begin an on-line backup. The only required parameter\nis an arbitrary user-defined label for the backup. (Typically this would be\nthe name under which the backup dump file will be stored.) If the optional\nsecond parameter is given as true, it specifies executing pg_backup_start\nas quickly as possible. This forces an immediate checkpoint which will\ncause a spike in I/O operations, slowing any concurrently executing\nqueries. +[PG_BACKUP_STOP] +declaration=[wait_for_archive boolean ] +category=System Administration Functions +description=Finishes performing an on-line backup. The desired contents of the backup\nlabel file and the tablespace map file are returned as part of the result\nof the function and must be written to files in the backup area. These\nfiles must not be written to the live data directory (doing so will cause\nPostgreSQL to fail to restart in the event of a crash). +[PG_BASETYPE] +declaration=regtype +category=Session Information Functions +description=Returns the OID of the base type of a domain identified by its type OID. If\nthe argument is the OID of a non-domain type, returns the argument as-is.\nReturns NULL if the argument is not a valid type OID. If there's a chain of\ndomain dependencies, it will recurse until finding the base type. +[PG_BLOCKING_PIDS] +declaration=integer +category=Session Information Functions +description=Returns an array of the process ID(s) of the sessions that are blocking the\nserver process with the specified process ID from acquiring a lock, or an\nempty array if there is no such server process or it is not blocked. +[PG_CANCEL_BACKEND] +declaration=pid integer +category=System Administration Functions +description=Cancels the current query of the session whose backend process has the\nspecified process ID. This is also allowed if the calling role is a member\nof the role whose backend is being canceled or the calling role has\nprivileges of pg_signal_backend, however only superusers can cancel\nsuperuser backends. +[PG_CHAR_TO_ENCODING] +declaration=encoding name +category=Session Information Functions +description=Converts the supplied encoding name into an integer representing the\ninternal identifier used in some system catalog tables. Returns -1 if an\nunknown encoding name is provided. +[PG_CLIENT_ENCODING] +declaration= +category=String Functions +description=Returns current client encoding name. +[PG_COLLATION_ACTUAL_VERSION] +declaration=oid +category=System Administration Functions +description=Returns the actual version of the collation object as it is currently\ninstalled in the operating system. If this is different from the value in\npg_collation.collversion, then objects depending on the collation might\nneed to be rebuilt. See also ALTER COLLATION. +[PG_COLLATION_IS_VISIBLE] +declaration=collation oid +category=Session Information Functions +description=Is collation visible in search path? +[PG_COLUMN_COMPRESSION] +declaration="any" +category=System Administration Functions +description=Shows the compression algorithm that was used to compress an individual\nvariable-length value. Returns NULL if the value is not compressed. +[PG_COLUMN_SIZE] +declaration="any" +category=System Administration Functions +description=Shows the number of bytes used to store any individual data value. If\napplied directly to a table column value, this reflects any compression\nthat was done. +[PG_COLUMN_TOAST_CHUNK_ID] +declaration="any" +category=System Administration Functions +description=Shows the chunk_id of an on-disk TOASTed value. Returns NULL if the value\nis un-TOASTed or not on-disk. See Section 65.2 for more information about\nTOAST. +[PG_CONF_LOAD_TIME] +declaration= +category=Session Information Functions +description=Returns the time when the server configuration files were last loaded. If\nthe current session was alive at the time, this will be the time when the\nsession itself re-read the configuration files (so the reading will vary a\nlittle in different sessions). Otherwise it is the time when the postmaster\nprocess re-read the configuration files. +[PG_CONTROL_CHECKPOINT] +declaration= +category=Session Information Functions +description=Returns information about current checkpoint state, as shown in Table 9.87. +[PG_CONTROL_INIT] +declaration= +category=Session Information Functions +description=Returns information about cluster initialization state, as shown in Table\n9.89. +[PG_CONTROL_RECOVERY] +declaration= +category=Session Information Functions +description=Returns information about recovery state, as shown in Table 9.90. +[PG_CONTROL_SYSTEM] +declaration= +category=Session Information Functions +description=Returns information about current control file state, as shown in Table\n9.88. +[PG_CONVERSION_IS_VISIBLE] +declaration=conversion oid +category=Session Information Functions +description=Is conversion visible in search path? +[PG_COPY_LOGICAL_REPLICATION_SLOT] +declaration=src_slot_name name, dst_slot_name name [, temporary boolean [, plugin name ]] +category=System Administration Functions +description=Copies an existing logical replication slot named src_slot_name to a\nlogical replication slot named dst_slot_name, optionally changing the\noutput plugin and persistence. The copied logical slot starts from the same\nLSN as the source logical slot. Both temporary and plugin are optional; if\nthey are omitted, the values of the source slot are used. +[PG_COPY_PHYSICAL_REPLICATION_SLOT] +declaration=src_slot_name name, dst_slot_name name [, temporary boolean ] +category=System Administration Functions +description=Copies an existing physical replication slot named src_slot_name to a\nphysical replication slot named dst_slot_name. The copied physical slot\nstarts to reserve WAL from the same LSN as the source slot. temporary is\noptional. If temporary is omitted, the same value as the source slot is\nused. +[PG_CREATE_LOGICAL_REPLICATION_SLOT] +declaration=slot_name name, plugin name [, temporary boolean, twophase boolean, failover boolean ] +category=System Administration Functions +description=Creates a new logical (decoding) replication slot named slot_name using the\noutput plugin plugin. The optional third parameter, temporary, when set to\ntrue, specifies that the slot should not be permanently stored to disk and\nis only meant for use by the current session. Temporary slots are also\nreleased upon any error. The optional fourth parameter, twophase, when set\nto true, specifies that the decoding of prepared transactions is enabled\nfor this slot. The optional fifth parameter, failover, when set to true,\nspecifies that this slot is enabled to be synced to the standbys so that\nlogical replication can be resumed after failover. A call to this function\nhas the same effect as the replication protocol command\nCREATE_REPLICATION_SLOT ... LOGICAL. +[PG_CREATE_PHYSICAL_REPLICATION_SLOT] +declaration=slot_name name [, immediately_reserve boolean, temporary boolean ] +category=System Administration Functions +description=Creates a new physical replication slot named slot_name. The optional\nsecond parameter, when true, specifies that the LSN for this replication\nslot be reserved immediately; otherwise the LSN is reserved on first\nconnection from a streaming replication client. Streaming changes from a\nphysical slot is only possible with the streaming-replication protocol -\nsee Section 53.4. The optional third parameter, temporary, when set to\ntrue, specifies that the slot should not be permanently stored to disk and\nis only meant for use by the current session. Temporary slots are also\nreleased upon any error. This function corresponds to the replication\nprotocol command CREATE_REPLICATION_SLOT ... PHYSICAL. +[PG_CREATE_RESTORE_POINT] +declaration=name text +category=System Administration Functions +description=Creates a named marker record in the write-ahead log that can later be used\nas a recovery target, and returns the corresponding write-ahead log\nlocation. The given name can then be used with recovery_target_name to\nspecify the point up to which recovery will proceed. Avoid creating\nmultiple restore points with the same name, since recovery will stop at the\nfirst one whose name matches the recovery target. +[PG_CURRENT_SNAPSHOT] +declaration= +category=Session Information Functions +description=Returns a current snapshot, a data structure showing which transaction IDs\nare now in-progress. Only top-level transaction IDs are included in the\nsnapshot; subtransaction IDs are not shown; see Section 66.3 for details. +[PG_CURRENT_WAL_FLUSH_LSN] +declaration= +category=System Administration Functions +description=Returns the current write-ahead log flush location (see notes below). +[PG_CURRENT_WAL_INSERT_LSN] +declaration= +category=System Administration Functions +description=Returns the current write-ahead log insert location (see notes below). +[PG_CURRENT_WAL_LSN] +declaration= +category=System Administration Functions +description=Returns the current write-ahead log write location (see notes below). +[PG_CURRENT_XACT_ID] +declaration= +category=Session Information Functions +description=Returns the current transaction's ID. It will assign a new one if the\ncurrent transaction does not have one already (because it has not performed\nany database updates); see Section 66.1 for details. If executed in a\nsubtransaction, this will return the top-level transaction ID; see Section\n66.3 for details. +[PG_CURRENT_XACT_ID_IF_ASSIGNED] +declaration= +category=Session Information Functions +description=Returns the current transaction's ID, or NULL if no ID is assigned yet.\n(It's best to use this variant if the transaction might otherwise be\nread-only, to avoid unnecessary consumption of an XID.) If executed in a\nsubtransaction, this will return the top-level transaction ID. +[PG_DATABASE_COLLATION_ACTUAL_VERSION] +declaration=oid +category=System Administration Functions +description=Returns the actual version of the database's collation as it is currently\ninstalled in the operating system. If this is different from the value in\npg_database.datcollversion, then objects depending on the collation might\nneed to be rebuilt. See also ALTER DATABASE. +[PG_DESCRIBE_OBJECT] +declaration=classid oid, objid oid, objsubid integer +category=Session Information Functions +description=Returns a textual description of a database object identified by catalog\nOID, object OID, and sub-object ID (such as a column number within a table;\nthe sub-object ID is zero when referring to a whole object). This\ndescription is intended to be human-readable, and might be translated,\ndepending on server configuration. This is especially useful to determine\nthe identity of an object referenced in the pg_depend catalog. This\nfunction returns NULL values for undefined objects. +[PG_DROP_REPLICATION_SLOT] +declaration=slot_name name +category=System Administration Functions +description=Drops the physical or logical replication slot named slot_name. Same as\nreplication protocol command DROP_REPLICATION_SLOT. For logical slots, this\nmust be called while connected to the same database the slot was created\non. +[PG_ENCODING_TO_CHAR] +declaration=encoding integer +category=Session Information Functions +description=Converts the integer used as the internal identifier of an encoding in some\nsystem catalog tables into a human-readable string. Returns an empty string\nif an invalid encoding number is provided. +[PG_EXPORT_SNAPSHOT] +declaration= +category=System Administration Functions +description=Saves the transaction's current snapshot and returns a text string\nidentifying the snapshot. This string must be passed (outside the database)\nto clients that want to import the snapshot. The snapshot is available for\nimport only until the end of the transaction that exported it. +[PG_FILENODE_RELATION] +declaration=tablespace oid, filenode oid +category=System Administration Functions +description=Returns a relation's OID given the tablespace OID and filenode it is stored\nunder. This is essentially the inverse mapping of pg_relation_filepath. For\na relation in the database's default tablespace, the tablespace can be\nspecified as zero. Returns NULL if no relation in the current database is\nassociated with the given values. +[PG_FUNCTION_IS_VISIBLE] +declaration=function oid +category=Session Information Functions +description=Is function visible in search path? (This also works for procedures and\naggregates.) +[PG_GET_CATALOG_FOREIGN_KEYS] +declaration= +category=Session Information Functions +description=Returns a set of records describing the foreign key relationships that\nexist within the PostgreSQL system catalogs. The fktable column contains\nthe name of the referencing catalog, and the fkcols column contains the\nname(s) of the referencing column(s). Similarly, the pktable column\ncontains the name of the referenced catalog, and the pkcols column contains\nthe name(s) of the referenced column(s). If is_array is true, the last\nreferencing column is an array, each of whose elements should match some\nentry in the referenced catalog. If is_opt is true, the referencing\ncolumn(s) are allowed to contain zeroes instead of a valid reference. +[PG_GET_CONSTRAINTDEF] +declaration=constraint oid [, pretty boolean ] +category=Session Information Functions +description=Reconstructs the creating command for a constraint. (This is a decompiled\nreconstruction, not the original text of the command.) +[PG_GET_EXPR] +declaration=expr pg_node_tree, relation oid [, pretty boolean ] +category=Session Information Functions +description=Decompiles the internal form of an expression stored in the system\ncatalogs, such as the default value for a column. If the expression might\ncontain Vars, specify the OID of the relation they refer to as the second\nparameter; if no Vars are expected, passing zero is sufficient. +[PG_GET_FUNCTIONDEF] +declaration=func oid +category=Session Information Functions +description=Reconstructs the creating command for a function or procedure. (This is a\ndecompiled reconstruction, not the original text of the command.) The\nresult is a complete CREATE OR REPLACE FUNCTION or CREATE OR REPLACE\nPROCEDURE statement. +[PG_GET_FUNCTION_ARGUMENTS] +declaration=func oid +category=Session Information Functions +description=Reconstructs the argument list of a function or procedure, in the form it\nwould need to appear in within CREATE FUNCTION (including default values). +[PG_GET_FUNCTION_IDENTITY_ARGUMENTS] +declaration=func oid +category=Session Information Functions +description=Reconstructs the argument list necessary to identify a function or\nprocedure, in the form it would need to appear in within commands such as\nALTER FUNCTION. This form omits default values. +[PG_GET_FUNCTION_RESULT] +declaration=func oid +category=Session Information Functions +description=Reconstructs the RETURNS clause of a function, in the form it would need to\nappear in within CREATE FUNCTION. Returns NULL for a procedure. +[PG_GET_INDEXDEF] +declaration=index oid [, column integer, pretty boolean ] +category=Session Information Functions +description=Reconstructs the creating command for an index. (This is a decompiled\nreconstruction, not the original text of the command.) If column is\nsupplied and is not zero, only the definition of that column is\nreconstructed. +[PG_GET_KEYWORDS] +declaration= +category=Session Information Functions +description=Returns a set of records describing the SQL keywords recognized by the\nserver. The word column contains the keyword. The catcode column contains a\ncategory code: U for an unreserved keyword, C for a keyword that can be a\ncolumn name, T for a keyword that can be a type or function name, or R for\na fully reserved keyword. The barelabel column contains true if the keyword\ncan be used as a "bare" column label in SELECT lists, or false if it can\nonly be used after AS. The catdesc column contains a possibly-localized\nstring describing the keyword's category. The baredesc column contains a\npossibly-localized string describing the keyword's column label status. +[PG_GET_OBJECT_ADDRESS] +declaration=type text, object_names text[], object_args text[] +category=Session Information Functions +description=Returns a row containing enough information to uniquely identify the\ndatabase object specified by a type code and object name and argument\narrays. The returned values are the ones that would be used in system\ncatalogs such as pg_depend; they can be passed to other system functions\nsuch as pg_describe_object or pg_identify_object. classid is the OID of the\nsystem catalog containing the object; objid is the OID of the object\nitself, and objsubid is the sub-object ID, or zero if none. This function\nis the inverse of pg_identify_object_as_address. Undefined objects are\nidentified with NULL values. +[PG_GET_PARTKEYDEF] +declaration=table oid +category=Session Information Functions +description=Reconstructs the definition of a partitioned table's partition key, in the\nform it would have in the PARTITION BY clause of CREATE TABLE. (This is a\ndecompiled reconstruction, not the original text of the command.) +[PG_GET_RULEDEF] +declaration=rule oid [, pretty boolean ] +category=Session Information Functions +description=Reconstructs the creating command for a rule. (This is a decompiled\nreconstruction, not the original text of the command.) +[PG_GET_SERIAL_SEQUENCE] +declaration=table text, column text +category=Session Information Functions +description=Returns the name of the sequence associated with a column, or NULL if no\nsequence is associated with the column. If the column is an identity\ncolumn, the associated sequence is the sequence internally created for that\ncolumn. For columns created using one of the serial types (serial,\nsmallserial, bigserial), it is the sequence created for that serial column\ndefinition. In the latter case, the association can be modified or removed\nwith ALTER SEQUENCE OWNED BY. (This function probably should have been\ncalled pg_get_owned_sequence; its current name reflects the fact that it\nhas historically been used with serial-type columns.) The first parameter\nis a table name with optional schema, and the second parameter is a column\nname. Because the first parameter potentially contains both schema and\ntable names, it is parsed per usual SQL rules, meaning it is lower-cased by\ndefault. The second parameter, being just a column name, is treated\nliterally and so has its case preserved. The result is suitably formatted\nfor passing to the sequence functions (see Section 9.17). +[PG_GET_STATISTICSOBJDEF] +declaration=statobj oid +category=Session Information Functions +description=Reconstructs the creating command for an extended statistics object. (This\nis a decompiled reconstruction, not the original text of the command.) +[PG_GET_TRIGGERDEF] +declaration=trigger oid [, pretty boolean ] +category=Session Information Functions +description=Reconstructs the creating command for a trigger. (This is a decompiled\nreconstruction, not the original text of the command.) +[PG_GET_USERBYID] +declaration=role oid +category=Session Information Functions +description=Returns a role's name given its OID. +[PG_GET_VIEWDEF] +declaration=view oid [, pretty boolean ] +category=Session Information Functions +description=Reconstructs the underlying SELECT command for a view or materialized view.\n(This is a decompiled reconstruction, not the original text of the\ncommand.) +[PG_GET_WAL_REPLAY_PAUSE_STATE] +declaration= +category=System Administration Functions +description=Returns recovery pause state. The return values are not paused if pause is\nnot requested, pause requested if pause is requested but recovery is not\nyet paused, and paused if the recovery is actually paused. +[PG_GET_WAL_RESOURCE_MANAGERS] +declaration= +category=System Administration Functions +description=Returns the currently-loaded WAL resource managers in the system. The\ncolumn rm_builtin indicates whether it's a built-in resource manager, or a\ncustom resource manager loaded by an extension. +[PG_GET_WAL_SUMMARIZER_STATE] +declaration= +category=Session Information Functions +description=Returns information about the progress of the WAL summarizer. If the WAL\nsummarizer has never run since the instance was started, then\nsummarized_tli and summarized_lsn will be 0 and 0/0 respectively;\notherwise, they will be the TLI and ending LSN of the last WAL summary file\nwritten to disk. If the WAL summarizer is currently running, pending_lsn\nwill be the ending LSN of the last record that it has consumed, which must\nalways be greater than or equal to summarized_lsn; if the WAL summarizer is\nnot running, it will be equal to summarized_lsn. summarizer_pid is the PID\nof the WAL summarizer process, if it is running, and otherwise NULL. +[PG_HAS_ROLE] +declaration=[ user name or oid, ] role text or oid, privilege text +category=Session Information Functions +description=Does user have privilege for role? Allowable privilege types are MEMBER,\nUSAGE, and SET. MEMBER denotes direct or indirect membership in the role\nwithout regard to what specific privileges may be conferred. USAGE denotes\nwhether the privileges of the role are immediately available without doing\nSET ROLE, while SET denotes whether it is possible to change to the role\nusing the SET ROLE command. WITH ADMIN OPTION or WITH GRANT OPTION can be\nadded to any of these privilege types to test whether the ADMIN privilege\nis held (all six spellings test the same thing). This function does not\nallow the special case of setting user to public, because the PUBLIC\npseudo-role can never be a member of real roles. +[PG_IDENTIFY_OBJECT] +declaration=classid oid, objid oid, objsubid integer +category=Session Information Functions +description=Returns a row containing enough information to uniquely identify the\ndatabase object specified by catalog OID, object OID and sub-object ID.\nThis information is intended to be machine-readable, and is never\ntranslated. type identifies the type of database object; schema is the\nschema name that the object belongs in, or NULL for object types that do\nnot belong to schemas; name is the name of the object, quoted if necessary,\nif the name (along with schema name, if pertinent) is sufficient to\nuniquely identify the object, otherwise NULL; identity is the complete\nobject identity, with the precise format depending on object type, and each\nname within the format being schema-qualified and quoted as necessary.\nUndefined objects are identified with NULL values. +[PG_IDENTIFY_OBJECT_AS_ADDRESS] +declaration=classid oid, objid oid, objsubid integer +category=Session Information Functions +description=Returns a row containing enough information to uniquely identify the\ndatabase object specified by catalog OID, object OID and sub-object ID. The\nreturned information is independent of the current server, that is, it\ncould be used to identify an identically named object in another server.\ntype identifies the type of database object; object_names and object_args\nare text arrays that together form a reference to the object. These three\nvalues can be passed to pg_get_object_address to obtain the internal\naddress of the object. +[PG_IMPORT_SYSTEM_COLLATIONS] +declaration=schema regnamespace +category=System Administration Functions +description=Adds collations to the system catalog pg_collation based on all the locales\nit finds in the operating system. This is what initdb uses; see Section\n23.2.2 for more details. If additional locales are installed into the\noperating system later on, this function can be run again to add collations\nfor the new locales. Locales that match existing entries in pg_collation\nwill be skipped. (But collation objects based on locales that are no longer\npresent in the operating system are not removed by this function.) The\nschema parameter would typically be pg_catalog, but that is not a\nrequirement; the collations could be installed into some other schema as\nwell. The function returns the number of new collation objects it created.\nUse of this function is restricted to superusers. +[PG_INDEXAM_HAS_PROPERTY] +declaration=am oid, property text +category=Session Information Functions +description=Tests whether an index access method has the named property. Access method\nproperties are listed in Table 9.77. NULL is returned if the property name\nis not known or does not apply to the particular object, or if the OID does\nnot identify a valid object. +[PG_INDEXES_SIZE] +declaration=regclass +category=System Administration Functions +description=Computes the total disk space used by indexes attached to the specified\ntable. +[PG_INDEX_COLUMN_HAS_PROPERTY] +declaration=index regclass, column integer, property text +category=Session Information Functions +description=Tests whether an index column has the named property. Common index column\nproperties are listed in Table 9.75. (Note that extension access methods\ncan define additional property names for their indexes.) NULL is returned\nif the property name is not known or does not apply to the particular\nobject, or if the OID or column number does not identify a valid object. +[PG_INDEX_HAS_PROPERTY] +declaration=index regclass, property text +category=Session Information Functions +description=Tests whether an index has the named property. Common index properties are\nlisted in Table 9.76. (Note that extension access methods can define\nadditional property names for their indexes.) NULL is returned if the\nproperty name is not known or does not apply to the particular object, or\nif the OID does not identify a valid object. +[PG_INPUT_ERROR_INFO] +declaration=string text, type text +category=Session Information Functions +description=Tests whether the given string is valid input for the specified data type;\nif not, return the details of the error that would have been thrown. If the\ninput is valid, the results are NULL. The inputs are the same as for\npg_input_is_valid. +[PG_INPUT_IS_VALID] +declaration=string text, type text +category=Session Information Functions +description=Tests whether the given string is valid input for the specified data type,\nreturning true or false. +[PG_IS_IN_RECOVERY] +declaration= +category=System Administration Functions +description=Returns true if recovery is still in progress. +[PG_IS_OTHER_TEMP_SCHEMA] +declaration=oid +category=Session Information Functions +description=Returns true if the given OID is the OID of another session's temporary\nschema. (This can be useful, for example, to exclude other sessions'\ntemporary tables from a catalog display.) +[PG_IS_WAL_REPLAY_PAUSED] +declaration= +category=System Administration Functions +description=Returns true if recovery pause is requested. +[PG_JIT_AVAILABLE] +declaration= +category=Session Information Functions +description=Returns true if a JIT compiler extension is available (see Chapter 30) and\nthe jit configuration parameter is set to on. +[PG_LAST_COMMITTED_XACT] +declaration= +category=Session Information Functions +description=Returns the transaction ID, commit timestamp and replication origin of the\nlatest committed transaction. +[PG_LAST_WAL_RECEIVE_LSN] +declaration= +category=System Administration Functions +description=Returns the last write-ahead log location that has been received and synced\nto disk by streaming replication. While streaming replication is in\nprogress this will increase monotonically. If recovery has completed then\nthis will remain static at the location of the last WAL record received and\nsynced to disk during recovery. If streaming replication is disabled, or if\nit has not yet started, the function returns NULL. +[PG_LAST_WAL_REPLAY_LSN] +declaration= +category=System Administration Functions +description=Returns the last write-ahead log location that has been replayed during\nrecovery. If recovery is still in progress this will increase\nmonotonically. If recovery has completed then this will remain static at\nthe location of the last WAL record applied during recovery. When the\nserver has been started normally without recovery, the function returns\nNULL. +[PG_LAST_XACT_REPLAY_TIMESTAMP] +declaration= +category=System Administration Functions +description=Returns the time stamp of the last transaction replayed during recovery.\nThis is the time at which the commit or abort WAL record for that\ntransaction was generated on the primary. If no transactions have been\nreplayed during recovery, the function returns NULL. Otherwise, if recovery\nis still in progress this will increase monotonically. If recovery has\ncompleted then this will remain static at the time of the last transaction\napplied during recovery. When the server has been started normally without\nrecovery, the function returns NULL. +[PG_LISTENING_CHANNELS] +declaration= +category=Session Information Functions +description=Returns the set of names of asynchronous notification channels that the\ncurrent session is listening to. +[PG_LOGICAL_SLOT_GET_BINARY_CHANGES] +declaration=slot_name name, upto_lsn pg_lsn, upto_nchanges integer, VARIADIC options text[] +category=System Administration Functions +description=Behaves just like the pg_logical_slot_get_changes() function, except that\nchanges are returned as bytea. +[PG_LOGICAL_SLOT_GET_CHANGES] +declaration=slot_name name, upto_lsn pg_lsn, upto_nchanges integer, VARIADIC options text[] +category=System Administration Functions +description=Returns changes in the slot slot_name, starting from the point from which\nchanges have been consumed last. If upto_lsn and upto_nchanges are NULL,\nlogical decoding will continue until end of WAL. If upto_lsn is non-NULL,\ndecoding will include only those transactions which commit prior to the\nspecified LSN. If upto_nchanges is non-NULL, decoding will stop when the\nnumber of rows produced by decoding exceeds the specified value. Note,\nhowever, that the actual number of rows returned may be larger, since this\nlimit is only checked after adding the rows produced when decoding each new\ntransaction commit. If the specified slot is a logical failover slot then\nthe function will not return until all physical slots specified in\nsynchronized_standby_slots have confirmed WAL receipt. +[PG_LOGICAL_SLOT_PEEK_BINARY_CHANGES] +declaration=slot_name name, upto_lsn pg_lsn, upto_nchanges integer, VARIADIC options text[] +category=System Administration Functions +description=Behaves just like the pg_logical_slot_peek_changes() function, except that\nchanges are returned as bytea. +[PG_LOGICAL_SLOT_PEEK_CHANGES] +declaration=slot_name name, upto_lsn pg_lsn, upto_nchanges integer, VARIADIC options text[] +category=System Administration Functions +description=Behaves just like the pg_logical_slot_get_changes() function, except that\nchanges are not consumed; that is, they will be returned again on future\ncalls. +[PG_LOG_BACKEND_MEMORY_CONTEXTS] +declaration=pid integer +category=System Administration Functions +description=Requests to log the memory contexts of the backend with the specified\nprocess ID. This function can send the request to backends and auxiliary\nprocesses except logger. These memory contexts will be logged at LOG\nmessage level. They will appear in the server log based on the log\nconfiguration set (see Section 19.8 for more information), but will not be\nsent to the client regardless of client_min_messages. +[PG_LOG_STANDBY_SNAPSHOT] +declaration= +category=System Administration Functions +description=Take a snapshot of running transactions and write it to WAL, without having\nto wait for bgwriter or checkpointer to log one. This is useful for logical\ndecoding on standby, as logical slot creation has to wait until such a\nrecord is replayed on the standby. +[PG_LS_ARCHIVE_STATUSDIR] +declaration= +category=System Administration Functions +description=Returns the name, size, and last modification time (mtime) of each ordinary\nfile in the server's WAL archive status directory (pg_wal/archive_status).\nFilenames beginning with a dot, directories, and other special files are\nexcluded. +[PG_LS_DIR] +declaration=dirname text [, missing_ok boolean, include_dot_dirs boolean ] +category=System Administration Functions +description=Returns the names of all files (and directories and other special files) in\nthe specified directory. The include_dot_dirs parameter indicates whether\n"." and ".." are to be included in the result set; the default is to\nexclude them. Including them can be useful when missing_ok is true, to\ndistinguish an empty directory from a non-existent directory. +[PG_LS_LOGDIR] +declaration= +category=System Administration Functions +description=Returns the name, size, and last modification time (mtime) of each ordinary\nfile in the server's log directory. Filenames beginning with a dot,\ndirectories, and other special files are excluded. +[PG_LS_LOGICALMAPDIR] +declaration= +category=System Administration Functions +description=Returns the name, size, and last modification time (mtime) of each ordinary\nfile in the server's pg_logical/mappings directory. Filenames beginning\nwith a dot, directories, and other special files are excluded. +[PG_LS_LOGICALSNAPDIR] +declaration= +category=System Administration Functions +description=Returns the name, size, and last modification time (mtime) of each ordinary\nfile in the server's pg_logical/snapshots directory. Filenames beginning\nwith a dot, directories, and other special files are excluded. +[PG_LS_REPLSLOTDIR] +declaration=slot_name text +category=System Administration Functions +description=Returns the name, size, and last modification time (mtime) of each ordinary\nfile in the server's pg_replslot/slot_name directory, where slot_name is\nthe name of the replication slot provided as input of the function.\nFilenames beginning with a dot, directories, and other special files are\nexcluded. +[PG_LS_TMPDIR] +declaration=[ tablespace oid ] +category=System Administration Functions +description=Returns the name, size, and last modification time (mtime) of each ordinary\nfile in the temporary file directory for the specified tablespace. If\ntablespace is not provided, the pg_default tablespace is examined.\nFilenames beginning with a dot, directories, and other special files are\nexcluded. +[PG_LS_WALDIR] +declaration= +category=System Administration Functions +description=Returns the name, size, and last modification time (mtime) of each ordinary\nfile in the server's write-ahead log (WAL) directory. Filenames beginning\nwith a dot, directories, and other special files are excluded. +[PG_MY_TEMP_SCHEMA] +declaration= +category=Session Information Functions +description=Returns the OID of the current session's temporary schema, or zero if it\nhas none (because it has not created any temporary tables). +[PG_NOTIFICATION_QUEUE_USAGE] +declaration= +category=Session Information Functions +description=Returns the fraction (0–1) of the asynchronous notification queue's\nmaximum size that is currently occupied by notifications that are waiting\nto be processed. See LISTEN and NOTIFY for more information. +[PG_OPCLASS_IS_VISIBLE] +declaration=opclass oid +category=Session Information Functions +description=Is operator class visible in search path? +[PG_OPERATOR_IS_VISIBLE] +declaration=operator oid +category=Session Information Functions +description=Is operator visible in search path? +[PG_OPFAMILY_IS_VISIBLE] +declaration=opclass oid +category=Session Information Functions +description=Is operator family visible in search path? +[PG_OPTIONS_TO_TABLE] +declaration=options_array text[] +category=Session Information Functions +description=Returns the set of storage options represented by a value from\npg_class.reloptions or pg_attribute.attoptions. +[PG_PARTITION_ANCESTORS] +declaration=regclass +category=System Administration Functions +description=Lists the ancestor relations of the given partition, including the relation\nitself. Returns no rows if the relation does not exist or is not a\npartition or partitioned table. +[PG_PARTITION_ROOT] +declaration=regclass +category=System Administration Functions +description=Returns the top-most parent of the partition tree to which the given\nrelation belongs. Returns NULL if the relation does not exist or is not a\npartition or partitioned table. +[PG_PARTITION_TREE] +declaration=regclass +category=System Administration Functions +description=Lists the tables or indexes in the partition tree of the given partitioned\ntable or partitioned index, with one row for each partition. Information\nprovided includes the OID of the partition, the OID of its immediate\nparent, a boolean value telling if the partition is a leaf, and an integer\ntelling its level in the hierarchy. The level value is 0 for the input\ntable or index, 1 for its immediate child partitions, 2 for their\npartitions, and so on. Returns no rows if the relation does not exist or is\nnot a partition or partitioned table. +[PG_POSTMASTER_START_TIME] +declaration= +category=Session Information Functions +description=Returns the time when the server started. +[PG_PROMOTE] +declaration=wait boolean DEFAULT true, wait_seconds integer DEFAULT 60 +category=System Administration Functions +description=Promotes a standby server to primary status. With wait set to true (the\ndefault), the function waits until promotion is completed or wait_seconds\nseconds have passed, and returns true if promotion is successful and false\notherwise. If wait is set to false, the function returns true immediately\nafter sending a SIGUSR1 signal to the postmaster to trigger promotion. +[PG_READ_BINARY_FILE] +declaration=filename text [, offset bigint, length bigint ] [, missing_ok boolean ] +category=System Administration Functions +description=Returns all or part of a file. This function is identical to pg_read_file\nexcept that it can read arbitrary binary data, returning the result as\nbytea not text; accordingly, no encoding checks are performed. +[PG_READ_FILE] +declaration=filename text [, offset bigint, length bigint ] [, missing_ok boolean ] +category=System Administration Functions +description=Returns all or part of a text file, starting at the given byte offset,\nreturning at most length bytes (less if the end of file is reached first).\nIf offset is negative, it is relative to the end of the file. If offset and\nlength are omitted, the entire file is returned. The bytes read from the\nfile are interpreted as a string in the database's encoding; an error is\nthrown if they are not valid in that encoding. +[PG_RELATION_FILENODE] +declaration=relation regclass +category=System Administration Functions +description=Returns the "filenode" number currently assigned to the specified relation.\nThe filenode is the base component of the file name(s) used for the\nrelation (see Section 65.1 for more information). For most relations the\nresult is the same as pg_class.relfilenode, but for certain system catalogs\nrelfilenode is zero and this function must be used to get the correct\nvalue. The function returns NULL if passed a relation that does not have\nstorage, such as a view. +[PG_RELATION_FILEPATH] +declaration=relation regclass +category=System Administration Functions +description=Returns the entire file path name (relative to the database cluster's data\ndirectory, PGDATA) of the relation. +[PG_RELATION_SIZE] +declaration=relation regclass [, fork text ] +category=System Administration Functions +description=Computes the disk space used by one "fork" of the specified relation. (Note\nthat for most purposes it is more convenient to use the higher-level\nfunctions pg_total_relation_size or pg_table_size, which sum the sizes of\nall forks.) With one argument, this returns the size of the main data fork\nof the relation. The second argument can be provided to specify which fork\nto examine: +[PG_RELOAD_CONF] +declaration= +category=System Administration Functions +description=Causes all processes of the PostgreSQL server to reload their configuration\nfiles. (This is initiated by sending a SIGHUP signal to the postmaster\nprocess, which in turn sends SIGHUP to each of its children.) You can use\nthe pg_file_settings, pg_hba_file_rules and pg_ident_file_mappings views to\ncheck the configuration files for possible errors, before reloading. +[PG_REPLICATION_ORIGIN_ADVANCE] +declaration=node_name text, lsn pg_lsn +category=System Administration Functions +description=Sets replication progress for the given node to the given location. This is\nprimarily useful for setting up the initial location, or setting a new\nlocation after configuration changes and similar. Be aware that careless\nuse of this function can lead to inconsistently replicated data. +[PG_REPLICATION_ORIGIN_CREATE] +declaration=node_name text +category=System Administration Functions +description=Creates a replication origin with the given external name, and returns the\ninternal ID assigned to it. +[PG_REPLICATION_ORIGIN_DROP] +declaration=node_name text +category=System Administration Functions +description=Deletes a previously-created replication origin, including any associated\nreplay progress. +[PG_REPLICATION_ORIGIN_OID] +declaration=node_name text +category=System Administration Functions +description=Looks up a replication origin by name and returns the internal ID. If no\nsuch replication origin is found, NULL is returned. +[PG_REPLICATION_ORIGIN_PROGRESS] +declaration=node_name text, flush boolean +category=System Administration Functions +description=Returns the replay location for the given replication origin. The parameter\nflush determines whether the corresponding local transaction will be\nguaranteed to have been flushed to disk or not. +[PG_REPLICATION_ORIGIN_SESSION_IS_SETUP] +declaration= +category=System Administration Functions +description=Returns true if a replication origin has been selected in the current\nsession. +[PG_REPLICATION_ORIGIN_SESSION_PROGRESS] +declaration=flush boolean +category=System Administration Functions +description=Returns the replay location for the replication origin selected in the\ncurrent session. The parameter flush determines whether the corresponding\nlocal transaction will be guaranteed to have been flushed to disk or not. +[PG_REPLICATION_ORIGIN_SESSION_RESET] +declaration= +category=System Administration Functions +description=Cancels the effects of pg_replication_origin_session_setup(). +[PG_REPLICATION_ORIGIN_SESSION_SETUP] +declaration=node_name text +category=System Administration Functions +description=Marks the current session as replaying from the given origin, allowing\nreplay progress to be tracked. Can only be used if no origin is currently\nselected. Use pg_replication_origin_session_reset to undo. +[PG_REPLICATION_ORIGIN_XACT_RESET] +declaration= +category=System Administration Functions +description=Cancels the effects of pg_replication_origin_xact_setup(). +[PG_REPLICATION_ORIGIN_XACT_SETUP] +declaration=origin_lsn pg_lsn, origin_timestamp timestamp with time zone +category=System Administration Functions +description=Marks the current transaction as replaying a transaction that has committed\nat the given LSN and timestamp. Can only be called when a replication\norigin has been selected using pg_replication_origin_session_setup. +[PG_REPLICATION_SLOT_ADVANCE] +declaration=slot_name name, upto_lsn pg_lsn +category=System Administration Functions +description=Advances the current confirmed position of a replication slot named\nslot_name. The slot will not be moved backwards, and it will not be moved\nbeyond the current insert location. Returns the name of the slot and the\nactual position that it was advanced to. The updated slot position\ninformation is written out at the next checkpoint if any advancing is done.\nSo in the event of a crash, the slot may return to an earlier position. If\nthe specified slot is a logical failover slot then the function will not\nreturn until all physical slots specified in synchronized_standby_slots\nhave confirmed WAL receipt. +[PG_ROTATE_LOGFILE] +declaration= +category=System Administration Functions +description=Signals the log-file manager to switch to a new output file immediately.\nThis works only when the built-in log collector is running, since otherwise\nthere is no log-file manager subprocess. +[PG_SAFE_SNAPSHOT_BLOCKING_PIDS] +declaration=integer +category=Session Information Functions +description=Returns an array of the process ID(s) of the sessions that are blocking the\nserver process with the specified process ID from acquiring a safe\nsnapshot, or an empty array if there is no such server process or it is not\nblocked. +[PG_SETTINGS_GET_FLAGS] +declaration=guc text +category=Session Information Functions +description=Returns an array of the flags associated with the given GUC, or NULL if it\ndoes not exist. The result is an empty array if the GUC exists but there\nare no flags to show. Only the most useful flags listed in Table 9.78 are\nexposed. +[PG_SIZE_BYTES] +declaration=text +category=System Administration Functions +description=Converts a size in human-readable format (as returned by pg_size_pretty)\ninto bytes. Valid units are bytes, B, kB, MB, GB, TB, and PB. +[PG_SNAPSHOT_XIP] +declaration=pg_snapshot +category=Session Information Functions +description=Returns the set of in-progress transaction IDs contained in a snapshot. +[PG_SNAPSHOT_XMAX] +declaration=pg_snapshot +category=Session Information Functions +description=Returns the xmax of a snapshot. +[PG_SNAPSHOT_XMIN] +declaration=pg_snapshot +category=Session Information Functions +description=Returns the xmin of a snapshot. +[PG_SPLIT_WALFILE_NAME] +declaration=file_name text +category=System Administration Functions +description=Extracts the sequence number and timeline ID from a WAL file name. +[PG_STATISTICS_OBJ_IS_VISIBLE] +declaration=stat oid +category=Session Information Functions +description=Is statistics object visible in search path? +[PG_STAT_FILE] +declaration=filename text [, missing_ok boolean ] +category=System Administration Functions +description=Returns a record containing the file's size, last access time stamp, last\nmodification time stamp, last file status change time stamp (Unix platforms\nonly), file creation time stamp (Windows only), and a flag indicating if it\nis a directory. +[PG_SWITCH_WAL] +declaration= +category=System Administration Functions +description=Forces the server to switch to a new write-ahead log file, which allows the\ncurrent file to be archived (assuming you are using continuous archiving).\nThe result is the ending write-ahead log location plus 1 within the\njust-completed write-ahead log file. If there has been no write-ahead log\nactivity since the last write-ahead log switch, pg_switch_wal does nothing\nand returns the start location of the write-ahead log file currently in\nuse. +[PG_SYNC_REPLICATION_SLOTS] +declaration= +category=System Administration Functions +description=Synchronize the logical failover replication slots from the primary server\nto the standby server. This function can only be executed on the standby\nserver. Temporary synced slots, if any, cannot be used for logical decoding\nand must be dropped after promotion. See Section 47.2.3 for details. Note\nthat this function cannot be executed if sync_replication_slots is enabled\nand the slotsync worker is already running to perform the synchronization\nof slots. +[PG_TABLESPACE_DATABASES] +declaration=tablespace oid +category=Session Information Functions +description=Returns the set of OIDs of databases that have objects stored in the\nspecified tablespace. If this function returns any rows, the tablespace is\nnot empty and cannot be dropped. To identify the specific objects\npopulating the tablespace, you will need to connect to the database(s)\nidentified by pg_tablespace_databases and query their pg_class catalogs. +[PG_TABLESPACE_LOCATION] +declaration=tablespace oid +category=Session Information Functions +description=Returns the file system path that this tablespace is located in. +[PG_TABLE_IS_VISIBLE] +declaration=table oid +category=Session Information Functions +description=Is table visible in search path? (This works for all types of relations,\nincluding views, materialized views, indexes, sequences and foreign\ntables.) +[PG_TABLE_SIZE] +declaration=regclass +category=System Administration Functions +description=Computes the disk space used by the specified table, excluding indexes (but\nincluding its TOAST table if any, free space map, and visibility map). +[PG_TERMINATE_BACKEND] +declaration=pid integer, timeout bigint DEFAULT 0 +category=System Administration Functions +description=Terminates the session whose backend process has the specified process ID.\nThis is also allowed if the calling role is a member of the role whose\nbackend is being terminated or the calling role has privileges of\npg_signal_backend, however only superusers can terminate superuser\nbackends. +[PG_TOTAL_RELATION_SIZE] +declaration=regclass +category=System Administration Functions +description=Computes the total disk space used by the specified table, including all\nindexes and TOAST data. The result is equivalent to pg_table_size +\npg_indexes_size. +[PG_TRIGGER_DEPTH] +declaration= +category=Session Information Functions +description=Returns the current nesting level of PostgreSQL triggers (0 if not called,\ndirectly or indirectly, from inside a trigger). +[PG_TS_CONFIG_IS_VISIBLE] +declaration=config oid +category=Session Information Functions +description=Is text search configuration visible in search path? +[PG_TS_DICT_IS_VISIBLE] +declaration=dict oid +category=Session Information Functions +description=Is text search dictionary visible in search path? +[PG_TS_PARSER_IS_VISIBLE] +declaration=parser oid +category=Session Information Functions +description=Is text search parser visible in search path? +[PG_TS_TEMPLATE_IS_VISIBLE] +declaration=template oid +category=Session Information Functions +description=Is text search template visible in search path? +[PG_TYPEOF] +declaration="any" +category=Session Information Functions +description=Returns the OID of the data type of the value that is passed to it. This\ncan be helpful for troubleshooting or dynamically constructing SQL queries.\nThe function is declared as returning regtype, which is an OID alias type\n(see Section 8.19); this means that it is the same as an OID for comparison\npurposes but displays as a type name. +[PG_TYPE_IS_VISIBLE] +declaration=type oid +category=Session Information Functions +description=Is type (or domain) visible in search path? +[PG_VISIBLE_IN_SNAPSHOT] +declaration=xid8, pg_snapshot +category=Session Information Functions +description=Is the given transaction ID visible according to this snapshot (that is,\nwas it completed before the snapshot was taken)? Note that this function\nwill not give the correct answer for a subtransaction ID (subxid); see\nSection 66.3 for details. +[PG_WALFILE_NAME] +declaration=lsn pg_lsn +category=System Administration Functions +description=Converts a write-ahead log location to the name of the WAL file holding\nthat location. +[PG_WALFILE_NAME_OFFSET] +declaration=lsn pg_lsn +category=System Administration Functions +description=Converts a write-ahead log location to a WAL file name and byte offset\nwithin that file. +[PG_WAL_LSN_DIFF] +declaration=lsn1 pg_lsn, lsn2 pg_lsn +category=System Administration Functions +description=Calculates the difference in bytes (lsn1 - lsn2) between two write-ahead\nlog locations. This can be used with pg_stat_replication or some of the\nfunctions shown in Table 9.95 to get the replication lag. +[PG_WAL_REPLAY_PAUSE] +declaration= +category=System Administration Functions +description=Request to pause recovery. A request doesn't mean that recovery stops right\naway. If you want a guarantee that recovery is actually paused, you need to\ncheck for the recovery pause state returned by\npg_get_wal_replay_pause_state(). Note that pg_is_wal_replay_paused()\nreturns whether a request is made. While recovery is paused, no further\ndatabase changes are applied. If hot standby is active, all new queries\nwill see the same consistent snapshot of the database, and no further query\nconflicts will be generated until recovery is resumed. +[PG_WAL_REPLAY_RESUME] +declaration= +category=System Administration Functions +description=Restarts recovery if it was paused. +[PG_WAL_SUMMARY_CONTENTS] +declaration=tli bigint, start_lsn pg_lsn, end_lsn pg_lsn +category=Session Information Functions +description=Returns one information about the contents of a single WAL summary file\nidentified by TLI and starting and ending LSNs. Each row with\nis_limit_block false indicates that the block identified by the remaining\noutput columns was modified by at least one WAL record within the range of\nrecords summarized by this file. Each row with is_limit_block true\nindicates either that (a) the relation fork was truncated to the length\ngiven by relblocknumber within the relevant range of WAL records or (b)\nthat the relation fork was created or dropped within the relevant range of\nWAL records; in such cases, relblocknumber will be zero. +[PG_XACT_COMMIT_TIMESTAMP] +declaration=xid +category=Session Information Functions +description=Returns the commit timestamp of a transaction. +[PG_XACT_COMMIT_TIMESTAMP_ORIGIN] +declaration=xid +category=Session Information Functions +description=Returns the commit timestamp and replication origin of a transaction. +[PG_XACT_STATUS] +declaration=xid8 +category=Session Information Functions +description=Reports the commit status of a recent transaction. The result is one of in\nprogress, committed, or aborted, provided that the transaction is recent\nenough that the system retains the commit status of that transaction. If it\nis old enough that no references to the transaction survive in the system\nand the commit status information has been discarded, the result is NULL.\nApplications might use this function, for example, to determine whether\ntheir transaction committed or aborted after the application and database\nserver become disconnected while a COMMIT is in progress. Note that\nprepared transactions are reported as in progress; applications must check\npg_prepared_xacts if they need to determine whether a transaction ID\nbelongs to a prepared transaction. +[PHRASETO_TSQUERY] +declaration=[ config regconfig, ] query text +category=Text Search Functions +description=Converts text to a tsquery, normalizing words according to the specified or\ndefault configuration. Any punctuation in the string is ignored (it does\nnot determine query operators). The resulting query matches phrases\ncontaining all non-stopwords in the text. +[PI] +declaration= +category=Numeric/Math Functions +description=Approximate value of π +[PLAINTO_TSQUERY] +declaration=[ config regconfig, ] query text +category=Text Search Functions +description=Converts text to a tsquery, normalizing words according to the specified or\ndefault configuration. Any punctuation in the string is ignored (it does\nnot determine query operators). The resulting query matches documents\ncontaining all non-stopwords in the text. +[POINT] +declaration=double precision, double precision +category=Geometric Functions +description=Constructs point from its coordinates. +[POLYGON] +declaration=box +category=Geometric Functions +description=Converts box to a 4-point polygon. +[POPEN] +declaration=path +category=Geometric Functions +description=Converts path to open form. +[POSITION1] +name=POSITION +declaration=substring text IN string text +category=String Functions +description=Returns first starting index of the specified substring within string, or\nzero if it's not present. +[POSITION2] +name=POSITION +declaration=substring bytea IN bytes bytea +category=Binary String Functions +description=Returns first starting index of the specified substring within bytes, or\nzero if it's not present. +[POSITION3] +name=POSITION +declaration=substring bit IN bits bit +category=Bit String Functions +description=Returns first starting index of the specified substring within bits, or\nzero if it's not present. +[QUERYTREE] +declaration=tsquery +category=Text Search Functions +description=Produces a representation of the indexable portion of a tsquery. A result\nthat is empty or just T indicates a non-indexable query. +[QUOTE_IDENT] +declaration=text +category=String Functions +description=Returns the given string suitably quoted to be used as an identifier in an\nSQL statement string. Quotes are added only if necessary (i.e., if the\nstring contains non-identifier characters or would be case-folded).\nEmbedded quotes are properly doubled. See also Example 41.1. +[QUOTE_LITERAL] +declaration=text category=String Functions -description=The PostgreSQL position function returns the location of a substring in a string. -[POWER] -declaration=m,n +description=Returns the given string suitably quoted to be used as a string literal in\nan SQL statement string. Embedded single-quotes and backslashes are\nproperly doubled. Note that quote_literal returns null on null input; if\nthe argument might be null, quote_nullable is often more suitable. See also\nExample 41.1. +[QUOTE_NULLABLE] +declaration=text +category=String Functions +description=Returns the given string suitably quoted to be used as a string literal in\nan SQL statement string; or, if the argument is null, returns NULL.\nEmbedded single-quotes and backslashes are properly doubled. See also\nExample 41.1. +[RADIANS] +declaration=double precision category=Numeric/Math Functions -description=The PostgreSQL power function returns m raised to the nth power. +description=Converts degrees to radians +[RADIUS] +declaration=circle +category=Geometric Functions +description=Computes radius of circle. [RANDOM] declaration= category=Numeric/Math Functions -description=The PostgreSQL random function can be used to return a random number or a random number within a range. +description=Returns a random value in the range 0.0 <= x < 1.0 +[RANDOM_NORMAL] +declaration=[ mean double precision [, stddev double precision ]] +category=Numeric/Math Functions +description=Returns a random value from the normal distribution with the given\nparameters; mean defaults to 0.0 and stddev defaults to 1.0 +[RANGE_MERGE1] +name=RANGE_MERGE +declaration=anyrange, anyrange +category=Range Functions +description=Computes the smallest range that includes both of the given ranges. +[RANGE_MERGE2] +name=RANGE_MERGE +declaration=anymultirange +category=Range Functions +description=Computes the smallest range that includes the entire multirange. +[RANK1] +name=RANK +declaration=args +category=Aggregate Functions +description=Computes the rank of the hypothetical row, with gaps; that is, the row\nnumber of the first row in its peer group. +[RANK2] +name=RANK +declaration= +category=Window Functions +description=Returns the rank of the current row, with gaps; that is, the row_number of\nthe first row in its peer group. +[REGEXP_COUNT] +declaration=string text, pattern text [, start integer [, flags text ] ] +category=String Functions +description=Returns the number of times the POSIX regular expression pattern matches in\nthe string; see Section 9.7.3. +[REGEXP_INSTR] +declaration=string text, pattern text [, start integer [, N integer [, endoption integer [, flags text [, subexpr integer ] ] ] ] ] +category=String Functions +description=Returns the position within string where the N'th match of the POSIX\nregular expression pattern occurs, or zero if there is no such match; see\nSection 9.7.3. +[REGEXP_LIKE] +declaration=string text, pattern text [, flags text ] +category=String Functions +description=Checks whether a match of the POSIX regular expression pattern occurs\nwithin string; see Section 9.7.3. +[REGEXP_MATCH] +declaration=string text, pattern text [, flags text ] +category=String Functions +description=Returns substrings within the first match of the POSIX regular expression\npattern to the string; see Section 9.7.3. +[REGEXP_MATCHES] +declaration=string text, pattern text [, flags text ] +category=String Functions +description=Returns substrings within the first match of the POSIX regular expression\npattern to the string, or substrings within all such matches if the g flag\nis used; see Section 9.7.3. +[REGEXP_REPLACE] +declaration=string text, pattern text, replacement text [, start integer ] [, flags text ] +category=String Functions +description=Replaces the substring that is the first match to the POSIX regular\nexpression pattern, or all such matches if the g flag is used; see Section\n9.7.3. +[REGEXP_SPLIT_TO_ARRAY] +declaration=string text, pattern text [, flags text ] +category=String Functions +description=Splits string using a POSIX regular expression as the delimiter, producing\nan array of results; see Section 9.7.3. +[REGEXP_SPLIT_TO_TABLE] +declaration=string text, pattern text [, flags text ] +category=String Functions +description=Splits string using a POSIX regular expression as the delimiter, producing\na set of results; see Section 9.7.3. +[REGEXP_SUBSTR] +declaration=string text, pattern text [, start integer [, N integer [, flags text [, subexpr integer ] ] ] ] +category=String Functions +description=Returns the substring within string that matches the N'th occurrence of the\nPOSIX regular expression pattern, or NULL if there is no such match; see\nSection 9.7.3. +[REGR_AVGX] +declaration=Y double precision, X double precision +category=Aggregate Functions +description=Computes the average of the independent variable, sum(X)/N. +[REGR_AVGY] +declaration=Y double precision, X double precision +category=Aggregate Functions +description=Computes the average of the dependent variable, sum(Y)/N. +[REGR_COUNT] +declaration=Y double precision, X double precision +category=Aggregate Functions +description=Computes the number of rows in which both inputs are non-null. +[REGR_R2] +declaration=Y double precision, X double precision +category=Aggregate Functions +description=Computes the square of the correlation coefficient. +[REGR_SXX] +declaration=Y double precision, X double precision +category=Aggregate Functions +description=Computes the "sum of squares" of the independent variable, sum(X^2) -\nsum(X)^2/N. +[REGR_SXY] +declaration=Y double precision, X double precision +category=Aggregate Functions +description=Computes the "sum of products" of independent times dependent variables,\nsum(X*Y) - sum(X) * sum(Y)/N. +[REGR_SYY] +declaration=Y double precision, X double precision +category=Aggregate Functions +description=Computes the "sum of squares" of the dependent variable, sum(Y^2) -\nsum(Y)^2/N. [REPEAT] -declaration=string,number +declaration=string text, number integer category=String Functions -description=The PostgreSQL repeat function repeats a string a specified number of times. +description=Repeats string the specified number of times. [REPLACE] -declaration=string,from_substring,to_substring +declaration=string text, from text, to text category=String Functions -description=The PostgreSQL replace function replaces all occurrences of a specified string. -[ROUND] -declaration=number,decimal_places -category=Numeric/Math Functions -description=The PostgreSQL round function returns a number rounded to a certain number of decimal places. +description=Replaces all occurrences in string of substring from with substring to. +[REVERSE] +declaration=text +category=String Functions +description=Reverses the order of the characters in the string. +[RIGHT] +declaration=string text, n integer +category=String Functions +description=Returns last n characters in the string, or when n is negative, returns all\nbut first |n| characters. +[ROW_NUMBER] +declaration= +category=Window Functions +description=Returns the number of the current row within its partition, counting from\n1. +[ROW_SECURITY_ACTIVE] +declaration=table text or oid +category=Session Information Functions +description=Is row-level security active for the specified table in the context of the\ncurrent user and current environment? +[ROW_TO_JSON] +declaration=record [, boolean ] +category=JSON Functions +description=Converts an SQL composite value to a JSON object. The behavior is the same\nas to_json except that line feeds will be added between top-level elements\nif the optional boolean parameter is true. [RPAD] -declaration=string,length,pad_string +declaration=string text, length integer [, fill text ] category=String Functions -description=The PostgreSQL rpad function returns a string that is right-padded with a specified string to a certain length. -[RTRIM] -declaration=string,trim_character +description=Extends the string to length length by appending the characters fill (a\nspace by default). If the string is already longer than length then it is\ntruncated. +[RTRIM1] +name=RTRIM +declaration=string text [, characters text ] category=String Functions -description=The PostgreSQL rtrim function removes all specified characters from the right-hand side of a string. +description=Removes the longest string containing only characters in characters (a\nspace by default) from the end of string. +[RTRIM2] +name=RTRIM +declaration=bytes bytea, bytesremoved bytea +category=Binary String Functions +description=Removes the longest string containing only bytes appearing in bytesremoved\nfrom the end of bytes. +[SCALE] +declaration=numeric +category=Numeric/Math Functions +description=Scale of the argument (the number of decimal digits in the fractional part) [SETSEED] -declaration=seed +declaration=double precision category=Numeric/Math Functions -description=The PostgreSQL setseed function can be used to set a seed for the next time that you call the random function. If you do not call setseed, PostgreSQL will use its own seed value. This may or may not be truly random. If you set the seed by calling the setseed function, then the random function will return a repeatable sequence of random numbers that is derived from the seed. -[SIGN] -declaration=number +description=Sets the seed for subsequent random() and random_normal() calls; argument\nmust be between -1.0 and 1.0, inclusive +[SETVAL] +declaration=regclass, bigint [, boolean ] +category=Sequence Manipulation Functions +description=Sets the sequence object's current value, and optionally its is_called\nflag. The two-parameter form sets the sequence's last_value field to the\nspecified value and sets its is_called field to true, meaning that the next\nnextval will advance the sequence before returning a value. The value that\nwill be reported by currval is also set to the specified value. In the\nthree-parameter form, is_called can be set to either true or false. true\nhas the same effect as the two-parameter form. If it is set to false, the\nnext nextval will return exactly the specified value, and sequence\nadvancement commences with the following nextval. Furthermore, the value\nreported by currval is not changed in this case. For example, +[SETWEIGHT1] +name=SETWEIGHT +declaration=vector tsvector, weight "char" +category=Text Search Functions +description=Assigns the specified weight to each element of the vector. +[SETWEIGHT2] +name=SETWEIGHT +declaration=vector tsvector, weight "char", lexemes text[] +category=Text Search Functions +description=Assigns the specified weight to elements of the vector that are listed in\nlexemes. The strings in lexemes are taken as lexemes as-is, without further\nprocessing. Strings that do not match any lexeme in vector are ignored. +[SET_BIT1] +name=SET_BIT +declaration=bytes bytea, n bigint, newvalue integer +category=Binary String Functions +description=Sets n'th bit in binary string to newvalue. +[SET_BIT2] +name=SET_BIT +declaration=bits bit, n integer, newvalue integer +category=Bit String Functions +description=Sets n'th bit in bit string to newvalue; the first (leftmost) bit is bit 0. +[SET_BYTE] +declaration=bytes bytea, n integer, newvalue integer +category=Binary String Functions +description=Sets n'th byte in binary string to newvalue. +[SET_CONFIG] +declaration=setting_name text, new_value text, is_local boolean +category=System Administration Functions +description=Sets the parameter setting_name to new_value, and returns that value. If\nis_local is true, the new value will only apply during the current\ntransaction. If you want the new value to apply for the rest of the current\nsession, use false instead. This function corresponds to the SQL command\nSET. +[SET_MASKLEN] +declaration=inet, integer +category=Network Address Functions +description=Sets the netmask length for an inet value. The address part does not\nchange. +[SHA224] +declaration=bytea +category=Binary String Functions +description=Computes the SHA-224 hash of the binary string. +[SHA256] +declaration=bytea +category=Binary String Functions +description=Computes the SHA-256 hash of the binary string. +[SHA384] +declaration=bytea +category=Binary String Functions +description=Computes the SHA-384 hash of the binary string. +[SHA512] +declaration=bytea +category=Binary String Functions +description=Computes the SHA-512 hash of the binary string. +[SHOBJ_DESCRIPTION] +declaration=object oid, catalog name +category=Session Information Functions +description=Returns the comment for a shared database object specified by its OID and\nthe name of the containing system catalog. This is just like\nobj_description except that it is used for retrieving comments on shared\nobjects (that is, databases, roles, and tablespaces). Some system catalogs\nare global to all databases within each cluster, and the descriptions for\nobjects in them are stored globally as well. +[SIN] +declaration=double precision category=Numeric/Math Functions -description=The PostgreSQL sign function returns a value indicating the sign of a number. -[SQRT] -declaration=number +description=Sine, argument in radians +[SIND] +declaration=double precision category=Numeric/Math Functions -description=The PostgreSQL sqrt function returns the square root of a number. +description=Sine, argument in degrees +[SINH] +declaration=double precision +category=Numeric/Math Functions +description=Hyperbolic sine +[SLOPE] +declaration=point, point +category=Geometric Functions +description=Computes slope of a line drawn through the two points. +[SPLIT_PART] +declaration=string text, delimiter text, n integer +category=String Functions +description=Splits string at occurrences of delimiter and returns the n'th field\n(counting from one), or when n is negative, returns the |n|'th-from-last\nfield. +[STARTS_WITH] +declaration=string text, prefix text +category=String Functions +description=Returns true if string starts with prefix. +[STATEMENT_TIMESTAMP] +declaration= +category=Date/Time Functions +description=Current date and time (start of current statement); see Section 9.9.5 +[STRING_TO_ARRAY] +declaration=string text, delimiter text [, null_string text ] +category=String Functions +description=Splits the string at occurrences of delimiter and forms the resulting\nfields into a text array. If delimiter is NULL, each character in the\nstring will become a separate element in the array. If delimiter is an\nempty string, then the string is treated as a single field. If null_string\nis supplied and is not NULL, fields matching that string are replaced by\nNULL. See also array_to_string. +[STRING_TO_TABLE] +declaration=string text, delimiter text [, null_string text ] +category=String Functions +description=Splits the string at occurrences of delimiter and returns the resulting\nfields as a set of text rows. If delimiter is NULL, each character in the\nstring will become a separate row of the result. If delimiter is an empty\nstring, then the string is treated as a single field. If null_string is\nsupplied and is not NULL, fields matching that string are replaced by NULL. +[STRIP] +declaration=tsvector +category=Text Search Functions +description=Removes positions and weights from the tsvector. [STRPOS] -declaration=string,substring -category=String Functions -description=The PostgreSQL strpos function returns the location of a substring in a string. -[SUBSTRING] -declaration=string,start_position,length -category=String Functions -description=The PostgreSQL substring function allows you to extract a substring from a string. -[SUM] -declaration=expression1, expression2, ... expression_n,aggregate_expression,tables,WHERE conditions -category=Numeric/Math Functions -description=The PostgreSQL sum function returns the summed value of an expression. -[TO_CHAR] -declaration=value,format_mask -category=Conversion Functions -description=The PostgreSQL to_char function converts a number or date to a string. -[TO_DATE] -declaration=string1,format_mask -category=Conversion Functions -description=The PostgreSQL to_date function converts a string to a date. -[TO_NUMBER] -declaration=string1,format_mask -category=Conversion Functions -description=The PostgreSQL to_number function converts a string to a number. +declaration=string text, substring text +category=String Functions +description=Returns first starting index of the specified substring within string, or\nzero if it's not present. (Same as position(substring in string), but note\nthe reversed argument order.) +[SUBSTR1] +name=SUBSTR +declaration=string text, start integer [, count integer ] +category=String Functions +description=Extracts the substring of string starting at the start'th character, and\nextending for count characters if that is specified. (Same as\nsubstring(string from start for count).) +[SUBSTR2] +name=SUBSTR +declaration=bytes bytea, start integer [, count integer ] +category=Binary String Functions +description=Extracts the substring of bytes starting at the start'th byte, and\nextending for count bytes if that is specified. (Same as substring(bytes\nfrom start for count).) +[SUBSTRING1] +name=SUBSTRING +declaration=string text [ FROM start integer ] [ FOR count integer ] +category=String Functions +description=Extracts the substring of string starting at the start'th character if that\nis specified, and stopping after count characters if that is specified.\nProvide at least one of start and count. +[SUBSTRING2] +name=SUBSTRING +declaration=bytes bytea [ FROM start integer ] [ FOR count integer ] +category=Binary String Functions +description=Extracts the substring of bytes starting at the start'th byte if that is\nspecified, and stopping after count bytes if that is specified. Provide at\nleast one of start and count. +[SUBSTRING3] +name=SUBSTRING +declaration=bits bit [ FROM start integer ] [ FOR count integer ] +category=Bit String Functions +description=Extracts the substring of bits starting at the start'th bit if that is\nspecified, and stopping after count bits if that is specified. Provide at\nleast one of start and count. +[SUPPRESS_REDUNDANT_UPDATES_TRIGGER] +declaration= +category=Trigger Functions +description=Suppresses do-nothing update operations. See below for details. +[TAN] +declaration=double precision +category=Numeric/Math Functions +description=Tangent, argument in radians +[TAND] +declaration=double precision +category=Numeric/Math Functions +description=Tangent, argument in degrees +[TANH] +declaration=double precision +category=Numeric/Math Functions +description=Hyperbolic tangent +[TEXT] +declaration=inet +category=Network Address Functions +description=Returns the unabbreviated IP address and netmask length as text. (This has\nthe same result as an explicit cast to text.) +[TIMEOFDAY] +declaration= +category=Date/Time Functions +description=Current date and time (like clock_timestamp, but as a text string); see\nSection 9.9.5 +[TO_JSONB] +declaration=anyelement +category=JSON Functions +description=Converts any SQL value to json or jsonb. Arrays and composites are\nconverted recursively to arrays and objects (multidimensional arrays become\narrays of arrays in JSON). Otherwise, if there is a cast from the SQL data\ntype to json, the cast function will be used to perform the conversion;[a]\notherwise, a scalar JSON value is produced. For any scalar other than a\nnumber, a Boolean, or a null value, the text representation will be used,\nwith escaping as necessary to make it a valid JSON string value. +[TO_REGCLASS] +declaration=text +category=Session Information Functions +description=Translates a textual relation name to its OID. A similar result is obtained\nby casting the string to type regclass (see Section 8.19); however, this\nfunction will return NULL rather than throwing an error if the name is not\nfound. +[TO_REGCOLLATION] +declaration=text +category=Session Information Functions +description=Translates a textual collation name to its OID. A similar result is\nobtained by casting the string to type regcollation (see Section 8.19);\nhowever, this function will return NULL rather than throwing an error if\nthe name is not found. +[TO_REGNAMESPACE] +declaration=text +category=Session Information Functions +description=Translates a textual schema name to its OID. A similar result is obtained\nby casting the string to type regnamespace (see Section 8.19); however,\nthis function will return NULL rather than throwing an error if the name is\nnot found. +[TO_REGOPER] +declaration=text +category=Session Information Functions +description=Translates a textual operator name to its OID. A similar result is obtained\nby casting the string to type regoper (see Section 8.19); however, this\nfunction will return NULL rather than throwing an error if the name is not\nfound or is ambiguous. +[TO_REGOPERATOR] +declaration=text +category=Session Information Functions +description=Translates a textual operator name (with parameter types) to its OID. A\nsimilar result is obtained by casting the string to type regoperator (see\nSection 8.19); however, this function will return NULL rather than throwing\nan error if the name is not found. +[TO_REGPROC] +declaration=text +category=Session Information Functions +description=Translates a textual function or procedure name to its OID. A similar\nresult is obtained by casting the string to type regproc (see Section\n8.19); however, this function will return NULL rather than throwing an\nerror if the name is not found or is ambiguous. +[TO_REGPROCEDURE] +declaration=text +category=Session Information Functions +description=Translates a textual function or procedure name (with argument types) to\nits OID. A similar result is obtained by casting the string to type\nregprocedure (see Section 8.19); however, this function will return NULL\nrather than throwing an error if the name is not found. +[TO_REGROLE] +declaration=text +category=Session Information Functions +description=Translates a textual role name to its OID. A similar result is obtained by\ncasting the string to type regrole (see Section 8.19); however, this\nfunction will return NULL rather than throwing an error if the name is not\nfound. +[TO_REGTYPE] +declaration=text +category=Session Information Functions +description=Parses a string of text, extracts a potential type name from it, and\ntranslates that name into a type OID. A syntax error in the string will\nresult in an error; but if the string is a syntactically valid type name\nthat happens not to be found in the catalogs, the result is NULL. A similar\nresult is obtained by casting the string to type regtype (see Section\n8.19), except that that will throw error for name not found. +[TO_REGTYPEMOD] +declaration=text +category=Session Information Functions +description=Parses a string of text, extracts a potential type name from it, and\ntranslates its type modifier, if any. A syntax error in the string will\nresult in an error; but if the string is a syntactically valid type name\nthat happens not to be found in the catalogs, the result is NULL. The\nresult is -1 if no type modifier is present. [TO_TIMESTAMP] -declaration=string1,format_mask -category=Conversion Functions -description=The PostgreSQL to_timestamp function converts a string to a timestamp. +declaration=double precision +category=Date/Time Functions +description=Convert Unix epoch (seconds since 1970-01-01 00:00:00+00) to timestamp with\ntime zone +[TO_TSQUERY] +declaration=[ config regconfig, ] query text +category=Text Search Functions +description=Converts text to a tsquery, normalizing words according to the specified or\ndefault configuration. The words must be combined by valid tsquery\noperators. +[TO_TSVECTOR] +declaration=[ config regconfig, ] document text +category=Text Search Functions +description=Converts text to a tsvector, normalizing words according to the specified\nor default configuration. Position information is included in the result. +[TRANSACTION_TIMESTAMP] +declaration= +category=Date/Time Functions +description=Current date and time (start of current transaction); see Section 9.9.5 [TRANSLATE] -declaration=string1,string_to_replace,replacement_string +declaration=string text, from text, to text category=String Functions -description=The PostgreSQL translate function replaces a sequence of characters in a string with another set of characters. However, it replaces a single character at a time. For example, it will replace the 1st character in the string_to_replace with the 1st character in the replacement_string. Then it will replace the 2nd character in the string_to_replace with the 2nd character in the replacement_string, and so on. -[TRIM] -declaration=leading,trailing,both,trim_character,string +description=Replaces each character in string that matches a character in the from set\nwith the corresponding character in the to set. If from is longer than to,\noccurrences of the extra characters in from are deleted. +[TRIM1] +name=TRIM +declaration=[ LEADING | TRAILING | BOTH ] [ characters text ] FROM string text category=String Functions -description=The PostgreSQL trim function removes all specified characters either from the beginning or the end of a string. -[TRUNC] -declaration=number,decimal_places +description=Removes the longest string containing only characters in characters (a\nspace by default) from the start, end, or both ends (BOTH is the default)\nof string. +[TRIM2] +name=TRIM +declaration=[ LEADING | TRAILING | BOTH ] bytesremoved bytea FROM bytes bytea +category=Binary String Functions +description=Removes the longest string containing only bytes appearing in bytesremoved\nfrom the start, end, or both ends (BOTH is the default) of bytes. +[TRIM_ARRAY] +declaration=array anyarray, n integer +category=Array Functions +description=Trims an array by removing the last n elements. If the array is\nmultidimensional, only the first dimension is trimmed. +[TRIM_SCALE] +declaration=numeric category=Numeric/Math Functions -description=The PostgreSQL trunc function returns a number truncated to a certain number of decimal places. -[UPPER] -declaration=string +description=Reduces the value's scale (number of fractional decimal digits) by removing\ntrailing zeroes +[TRUNC] +declaration=macaddr +category=Network Address Functions +description=Sets the last 3 bytes of the address to zero. The remaining prefix can be\nassociated with a particular manufacturer (using data not included in\nPostgreSQL). +[TSQUERY_PHRASE] +declaration=query1 tsquery, query2 tsquery +category=Text Search Functions +description=Constructs a phrase query that searches for matches of query1 and query2 at\nsuccessive lexemes (same as <-> operator). +[TSVECTOR_TO_ARRAY] +declaration=tsvector +category=Text Search Functions +description=Converts a tsvector to an array of lexemes. +[TSVECTOR_UPDATE_TRIGGER] +declaration= +category=Trigger Functions +description=Automatically updates a tsvector column from associated plain-text document\ncolumn(s). The text search configuration to use is specified by name as a\ntrigger argument. See Section 12.4.3 for details. +[TSVECTOR_UPDATE_TRIGGER_COLUMN] +declaration= +category=Trigger Functions +description=Automatically updates a tsvector column from associated plain-text document\ncolumn(s). The text search configuration to use is taken from a regconfig\ncolumn of the table. See Section 12.4.3 for details. +[TS_DEBUG] +declaration=[ config regconfig, ] document text +category=Text Search Functions +description=Extracts and normalizes tokens from the document according to the specified\nor default text search configuration, and returns information about how\neach token was processed. See Section 12.8.1 for details. +[TS_DELETE] +declaration=vector tsvector, lexeme text +category=Text Search Functions +description=Removes any occurrence of the given lexeme from the vector. The lexeme\nstring is treated as a lexeme as-is, without further processing. +[TS_FILTER] +declaration=vector tsvector, weights "char"[] +category=Text Search Functions +description=Selects only elements with the given weights from the vector. +[TS_HEADLINE] +declaration=[ config regconfig, ] document text, query tsquery [, options text ] +category=Text Search Functions +description=Displays, in an abbreviated form, the match(es) for the query in the\ndocument, which must be raw text not a tsvector. Words in the document are\nnormalized according to the specified or default configuration before\nmatching to the query. Use of this function is discussed in Section 12.3.4,\nwhich also describes the available options. +[TS_LEXIZE] +declaration=dict regdictionary, token text +category=Text Search Functions +description=Returns an array of replacement lexemes if the input token is known to the\ndictionary, or an empty array if the token is known to the dictionary but\nit is a stop word, or NULL if it is not a known word. See Section 12.8.3\nfor details. +[TS_PARSE] +declaration=parser_name text, document text +category=Text Search Functions +description=Extracts tokens from the document using the named parser. See Section\n12.8.2 for details. +[TS_RANK] +declaration=[ weights real[], ] vector tsvector, query tsquery [, normalization integer ] +category=Text Search Functions +description=Computes a score showing how well the vector matches the query. See Section\n12.3.3 for details. +[TS_RANK_CD] +declaration=[ weights real[], ] vector tsvector, query tsquery [, normalization integer ] +category=Text Search Functions +description=Computes a score showing how well the vector matches the query, using a\ncover density algorithm. See Section 12.3.3 for details. +[TS_REWRITE] +declaration=query tsquery, target tsquery, substitute tsquery +category=Text Search Functions +description=Replaces occurrences of target with substitute within the query. See\nSection 12.4.2.1 for details. +[TS_STAT] +declaration=sqlquery text [, weights text ] +category=Text Search Functions +description=Executes the sqlquery, which must return a single tsvector column, and\nreturns statistics about each distinct lexeme contained in the data. See\nSection 12.4.4 for details. +[TS_TOKEN_TYPE] +declaration=parser_name text +category=Text Search Functions +description=Returns a table that describes each type of token the named parser can\nrecognize. See Section 12.8.2 for details. +[TXID_CURRENT] +declaration= +category=Session Information Functions +description=See pg_current_xact_id(). +[TXID_CURRENT_IF_ASSIGNED] +declaration= +category=Session Information Functions +description=See pg_current_xact_id_if_assigned(). +[TXID_CURRENT_SNAPSHOT] +declaration= +category=Session Information Functions +description=See pg_current_snapshot(). +[TXID_SNAPSHOT_XIP] +declaration=txid_snapshot +category=Session Information Functions +description=See pg_snapshot_xip(). +[TXID_SNAPSHOT_XMAX] +declaration=txid_snapshot +category=Session Information Functions +description=See pg_snapshot_xmax(). +[TXID_SNAPSHOT_XMIN] +declaration=txid_snapshot +category=Session Information Functions +description=See pg_snapshot_xmin(). +[TXID_STATUS] +declaration=bigint +category=Session Information Functions +description=See pg_xact_status(). +[TXID_VISIBLE_IN_SNAPSHOT] +declaration=bigint, txid_snapshot +category=Session Information Functions +description=See pg_visible_in_snapshot(). +[UNICODE_ASSIGNED] +declaration=text +category=String Functions +description=Returns true if all characters in the string are assigned Unicode\ncodepoints; false otherwise. This function can only be used when the server\nencoding is UTF8. +[UNICODE_VERSION] +declaration= +category=Session Information Functions +description=Returns a string representing the version of Unicode used by PostgreSQL. +[UNISTR] +declaration=text category=String Functions -description=The PostgreSQL upper function converts all characters in the specified string to uppercase. -[XMLCONCAT] -declaration=xml -category=XML -description=concatenates a list of individual XML values to create a single value containing an XML content fragment \ No newline at end of file +description=Evaluate escaped Unicode characters in the argument. Unicode characters can\nbe specified as \XXXX (4 hexadecimal digits), \+XXXXXX (6 hexadecimal\ndigits), \uXXXX (4 hexadecimal digits), or \UXXXXXXXX (8 hexadecimal\ndigits). To specify a backslash, write two backslashes. All other\ncharacters are taken literally. +[UNNEST1] +name=UNNEST +declaration=tsvector +category=Text Search Functions +description=Expands a tsvector into a set of rows, one per lexeme. +[UNNEST2] +name=UNNEST +declaration=anyarray +category=Array Functions +description=Expands an array into a set of rows. The array's elements are read out in\nstorage order. +[UNNEST3] +name=UNNEST +declaration=anymultirange +category=Range Functions +description=Expands a multirange into a set of ranges in ascending order. +[UPPER1] +name=UPPER +declaration=text +category=String Functions +description=Converts the string to all upper case, according to the rules of the\ndatabase's locale. +[UPPER2] +name=UPPER +declaration=anyrange +category=Range Functions +description=Extracts the upper bound of the range (NULL if the range is empty or has no\nupper bound). +[UPPER3] +name=UPPER +declaration=anymultirange +category=Range Functions +description=Extracts the upper bound of the multirange (NULL if the multirange is empty\nor has no upper bound). +[UPPER_INC1] +name=UPPER_INC +declaration=anyrange +category=Range Functions +description=Is the range's upper bound inclusive? +[UPPER_INC2] +name=UPPER_INC +declaration=anymultirange +category=Range Functions +description=Is the multirange's upper bound inclusive? +[UPPER_INF1] +name=UPPER_INF +declaration=anyrange +category=Range Functions +description=Does the range have no upper bound? (An upper bound of Infinity returns\nfalse.) +[UPPER_INF2] +name=UPPER_INF +declaration=anymultirange +category=Range Functions +description=Does the multirange have no upper bound? (An upper bound of Infinity\nreturns false.) +[VARIANCE] +declaration=numeric_type +category=Aggregate Functions +description=This is a historical alias for var_samp. +[VERSION] +declaration= +category=Session Information Functions +description=Returns a string describing the PostgreSQL server's version. You can also\nget this information from server_version, or for a machine-readable version\nuse server_version_num. Software developers should use server_version_num\n(available since 8.2) or PQserverVersion instead of parsing the text\nversion. +[WEBSEARCH_TO_TSQUERY] +declaration=[ config regconfig, ] query text +category=Text Search Functions +description=Converts text to a tsquery, normalizing words according to the specified or\ndefault configuration. Quoted word sequences are converted to phrase tests.\nThe word "or" is understood as producing an OR operator, and a dash\nproduces a NOT operator; other punctuation is ignored. This approximates\nthe behavior of some common web search tools. +[WIDTH] +declaration=box +category=Geometric Functions +description=Computes horizontal size of box. +[XMLAGG] +declaration=xml ORDER BY input_sort_columns +category=Aggregate Functions +description=Concatenates the non-null XML input values (see Section 9.15.1.8). \ No newline at end of file diff --git a/out/gds32-14.1.dll b/out/gds32-14.1.dll deleted file mode 100644 index 0fa2ab47d..000000000 Binary files a/out/gds32-14.1.dll and /dev/null differ diff --git a/out/heidisql.iss b/out/heidisql.iss index 4fe22f8dc..bf9fbe812 100644 --- a/out/heidisql.iss +++ b/out/heidisql.iss @@ -13,8 +13,8 @@ #define ProgVerMinor #define ProgVerRevision #define ProgVerBuild -#define ProgVersion GetVersionComponents(AddBackslash(SourcePath) + ProgNameLower + "32.exe", ProgVerMajor, ProgVerMinor, ProgVerRevision, ProgVerBuild) -#define ProgShortVersion Str(ProgVerMajor) + "." + Str(ProgVerMinor) +#define ProgVersion GetVersionComponents(AddBackslash(SourcePath) + ProgNameLower + "64.exe", ProgVerMajor, ProgVerMinor, ProgVerRevision, ProgVerBuild) +#define ProgVersionStr Str(ProgVerMajor) + "." + Str(ProgVerMinor) + "." + Str(ProgVerRevision) + "." + Str(ProgVerBuild) [Languages] Name: "en"; MessagesFile: "compiler:Default.isl" @@ -33,6 +33,7 @@ Name: "hu"; MessagesFile: "compiler:Languages\Hungarian.isl" Name: "is"; MessagesFile: "compiler:Languages\Icelandic.isl" Name: "it"; MessagesFile: "compiler:Languages\Italian.isl" Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl" +Name: "kr"; MessagesFile: "compiler:Languages\Korean.isl" Name: "no"; MessagesFile: "compiler:Languages\Norwegian.isl" Name: "pl"; MessagesFile: "compiler:Languages\Polish.isl" Name: "pt"; MessagesFile: "compiler:Languages\Portuguese.isl" @@ -41,6 +42,8 @@ Name: "ru"; MessagesFile: "compiler:Languages\Russian.isl" Name: "sk"; MessagesFile: "compiler:Languages\Slovak.isl" Name: "sl"; MessagesFile: "compiler:Languages\Slovenian.isl" Name: "es"; MessagesFile: "compiler:Languages\Spanish.isl" +Name: "se"; MessagesFile: "compiler:Languages\Swedish.isl" +Name: "ta"; MessagesFile: "compiler:Languages\Tamil.isl" Name: "tr"; MessagesFile: "compiler:Languages\Turkish.isl" Name: "uk"; MessagesFile: "compiler:Languages\Ukrainian.isl" @@ -50,7 +53,7 @@ AppName={#ProgName} AppVerName={#ProgName} {#ProgVersion} VersionInfoVersion={#ProgVersion} ; Displayed on the "Support" dialog of the Add/Remove Programs Control Panel applet: -AppVersion={#ProgShortVersion} +AppVersion={#ProgVersionStr} AppPublisher=Ansgar Becker AppPublisherURL={#WebSite} AppSupportURL={#WebSite}forum.php @@ -70,79 +73,57 @@ WizardStyle=modern WizardImageFile={#ResourceDir}installer-logo.bmp WizardSmallImageFile={#ResourceDir}installer-small-logo.bmp OutputDir={#OutDir} -OutputBaseFilename={#ProgName}_{#ProgShortVersion}_Setup +OutputBaseFilename={#ProgName}_{#ProgVersionStr}_Setup UninstallDisplayIcon={app}\{#ProgExeName} SetupIconFile={#ResourceDir}mainicon.ico -ArchitecturesInstallIn64BitMode=x64 +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible UsePreviousAppDir=yes DirExistsWarning=auto PrivilegesRequired=admin PrivilegesRequiredOverridesAllowed=commandline dialog -SignedUninstaller=yes -SignTool=signtool $f +;SignedUninstaller=yes +;SignTool=signtool $f [Tasks] Name: "desktopicon"; Description: "Create a &desktop icon"; GroupDescription: "Local options:"; MinVersion: 4,4 -Name: "install_snippets"; Description: "Create example SQL snippet files in {#SnippetsDir}"; GroupDescription: "Local options:"; +Name: "install_snippets"; Description: "Create example SQL snippet files in {#SnippetsDir}"; GroupDescription: "Local options:"; Flags: unchecked Name: "associatesqlfiles"; Description: "Associate .&SQL files with {#ProgName}"; GroupDescription: "Local options:"; Name: "activate_updatechecks"; Description: "Automatically check {#WebSite} for updates"; GroupDescription: "Telemetry:"; Name: "activate_statistics"; Description: "Automatically report client and server versions on {#WebSite}"; GroupDescription: "Telemetry:"; -[InstallDelete] -Type: files; Name: "{app}\heidisql32.exe" -Type: files; Name: "{app}\libpq.dll" - [Files] -Source: "{#ProgNameLower}64.exe"; DestDir: "{app}"; DestName: "{#ProgExeName}"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "{#ProgNameLower}32.exe"; DestDir: "{app}"; DestName: "{#ProgExeName}"; Check: not Is64BitInstallMode; Flags: ignoreversion +Source: "{#ProgNameLower}64.exe"; DestDir: "{app}"; DestName: "{#ProgExeName}"; Flags: ignoreversion Source: "license.txt"; DestDir: "{app}"; Flags: ignoreversion Source: "gpl.txt"; DestDir: "{app}"; Flags: ignoreversion -Source: "plugins64\*.dll"; DestDir: "{app}\plugins"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "plugins32\*.dll"; DestDir: "{app}\plugins"; Check: not Is64BitInstallMode; Flags: ignoreversion +Source: "plugins64\*.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion Source: "Snippets\*.sql"; DestDir: "{#SnippetsDir}"; Tasks: install_snippets -Source: "plink-64.exe"; DestDir: "{app}"; DestName: "plink.exe"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "plink-32.exe"; DestDir: "{app}"; DestName: "plink.exe"; Check: not Is64BitInstallMode; Flags: ignoreversion +Source: "plink-64.exe"; DestDir: "{app}"; DestName: "plink.exe"; Flags: ignoreversion +Source: "plink-0.81-64.exe"; DestDir: "{app}"; DestName: "plink-0.81.exe"; Flags: ignoreversion ; OpenSSL libraries, used by Indy HTTP: -Source: "libeay32-64.dll"; DestDir: "{app}"; DestName: "libeay32.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libeay32-32.dll"; DestDir: "{app}"; DestName: "libeay32.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion -Source: "ssleay32-64.dll"; DestDir: "{app}"; DestName: "ssleay32.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "ssleay32-32.dll"; DestDir: "{app}"; DestName: "ssleay32.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion +Source: "libeay32-64.dll"; DestDir: "{app}"; DestName: "libeay32.dll"; Flags: ignoreversion +Source: "ssleay32-64.dll"; DestDir: "{app}"; DestName: "ssleay32.dll"; Flags: ignoreversion ; MySQL + MariaDB: -Source: "libmariadb64.dll"; DestDir: "{app}"; DestName: "libmariadb.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libmariadb32.dll"; DestDir: "{app}"; DestName: "libmariadb.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion -Source: "libmysql64.dll"; DestDir: "{app}"; DestName: "libmysql.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libmysql32.dll"; DestDir: "{app}"; DestName: "libmysql.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion -Source: "libmysql-6.1-64.dll"; DestDir: "{app}"; DestName: "libmysql-6.1.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libmysql-6.1-32.dll"; DestDir: "{app}"; DestName: "libmysql-6.1.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion +Source: "libmariadb-64.dll"; DestDir: "{app}"; DestName: "libmariadb.dll"; Flags: ignoreversion +Source: "libmysql-64.dll"; DestDir: "{app}"; DestName: "libmysql.dll"; Flags: ignoreversion +Source: "libmysql-6.1-64.dll"; DestDir: "{app}"; DestName: "libmysql-6.1.dll"; Flags: ignoreversion +Source: "libmysql-8.4.0-64.dll"; DestDir: "{app}"; DestName: "libmysql-8.4.0.dll"; Flags: ignoreversion +Source: "libmysql-9.4.0-64.dll"; DestDir: "{app}"; DestName: "libmysql-9.4.0.dll"; Flags: ignoreversion ; PostgreSQL: -Source: "libpq-10-64.dll"; DestDir: "{app}"; DestName: "libpq-10.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libpq-10-32.dll"; DestDir: "{app}"; DestName: "libpq-10.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion -Source: "libpq-12-64.dll"; DestDir: "{app}"; DestName: "libpq-12.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libpq-15-64.dll"; DestDir: "{app}"; DestName: "libpq-15.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libintl-8-64.dll"; DestDir: "{app}"; DestName: "libintl-8.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libintl-8-32.dll"; DestDir: "{app}"; DestName: "libintl-8.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion -Source: "libintl-9-64.dll"; DestDir: "{app}"; DestName: "libintl-9.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libssl-1_1-x64.dll"; DestDir: "{app}"; DestName: "libssl-1_1-x64.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libssl-1_1-32.dll"; DestDir: "{app}"; DestName: "libssl-1_1.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion -Source: "libssl-3-x64.dll"; DestDir: "{app}"; DestName: "libssl-3-x64.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libcrypto-1_1-x64.dll"; DestDir: "{app}"; DestName: "libcrypto-1_1-x64.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libcrypto-1_1-32.dll"; DestDir: "{app}"; DestName: "libcrypto-1_1.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion -Source: "libcrypto-3-x64.dll"; DestDir: "{app}"; DestName: "libcrypto-3-x64.dll"; Check: Is64BitInstallMode; Flags: ignoreversion +Source: "libpq-15-64.dll"; DestDir: "{app}"; DestName: "libpq-15.dll"; Flags: ignoreversion +Source: "libpq-17-64.dll"; DestDir: "{app}"; DestName: "libpq-17.dll"; Flags: ignoreversion +Source: "libintl-9-64.dll"; DestDir: "{app}"; DestName: "libintl-9.dll"; Flags: ignoreversion +Source: "libssl-3-x64.dll"; DestDir: "{app}"; DestName: "libssl-3-x64.dll"; Flags: ignoreversion +Source: "libcrypto-3-x64.dll"; DestDir: "{app}"; DestName: "libcrypto-3-x64.dll"; Flags: ignoreversion Source: "LICENSE-openssl"; DestDir: "{app}"; Flags: ignoreversion -Source: "libiconv-2-64.dll"; DestDir: "{app}"; DestName: "libiconv-2.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "libiconv-2-32.dll"; DestDir: "{app}"; DestName: "libiconv-2.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion -Source: "libwinpthread-1-64.dll"; DestDir: "{app}"; DestName: "libwinpthread-1.dll"; Check: Is64BitInstallMode; Flags: ignoreversion +Source: "libiconv-2-64.dll"; DestDir: "{app}"; DestName: "libiconv-2.dll"; Flags: ignoreversion +Source: "libwinpthread-1-64.dll"; DestDir: "{app}"; DestName: "libwinpthread-1.dll"; Flags: ignoreversion ; SQLite: -Source: "sqlite3-64.dll"; DestDir: "{app}"; DestName: "sqlite3.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "sqlite3-32.dll"; DestDir: "{app}"; DestName: "sqlite3.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion +Source: "sqlite3-64.dll"; DestDir: "{app}"; DestName: "sqlite3.dll"; Flags: ignoreversion +Source: "sqlite3mc-64.dll"; DestDir: "{app}"; DestName: "sqlite3mc.dll"; Flags: ignoreversion ; Interbase/Firebird: -Source: "ibclient64-14.1.dll"; DestDir: "{app}"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "gds32-14.1.dll"; DestDir: "{app}"; Check: not Is64BitInstallMode; Flags: ignoreversion -Source: "fbclient-4.0-64.dll"; DestDir: "{app}"; DestName: "fbclient-4.0.dll"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: "fbclient-4.0-32.dll"; DestDir: "{app}"; DestName: "fbclient-4.0.dll"; Check: not Is64BitInstallMode; Flags: ignoreversion -; VC redistributable -Source: VC_redist.x64.exe; DestDir: "{app}"; Check: Is64BitInstallMode; Flags: ignoreversion -Source: VC_redist.x86.exe; DestDir: "{app}"; Check: not Is64BitInstallMode; Flags: ignoreversion +Source: "ibclient64-14.1.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "fbclient-4.0-64.dll"; DestDir: "{app}"; DestName: "fbclient-4.0.dll"; Flags: ignoreversion ; SQL function definitions Source: "functions-*.ini"; DestDir: "{app}"; Flags: ignoreversion @@ -164,8 +145,6 @@ Root: HKCU; Subkey: "Software\{#ProgName}"; ValueType: dword; ValueName: "DoUsag [Run] Filename: "{app}\{#ProgExeName}"; Description: "Launch {#ProgName}"; Flags: nowait postinstall skipifsilent -Filename: "{app}\VC_redist.x64.exe"; Parameters: "/q /norestart /q:a /c:""VC_RED~2.EXE /q:a /c:""""msiexec /i vcredist.msi /qn"""" """; Check: Is64BitInstallMode; StatusMsg: Installing VC++ 2019 x64 Redistributables... -Filename: "{app}\VC_redist.x86.exe"; Parameters: "/q /norestart /q:a /c:""VC_RED~1.EXE /q:a /c:""""msiexec /i vcredist.msi /qn"""" """; Check: not Is64BitInstallMode; StatusMsg: Installing VC++ 2019 x86 Redistributables... [Code] var diff --git a/out/libcrypto-1_1-32.dll b/out/libcrypto-1_1-32.dll deleted file mode 100644 index d407b5123..000000000 Binary files a/out/libcrypto-1_1-32.dll and /dev/null differ diff --git a/out/libcrypto-1_1-x64.dll b/out/libcrypto-1_1-x64.dll deleted file mode 100644 index 80f1ab3eb..000000000 Binary files a/out/libcrypto-1_1-x64.dll and /dev/null differ diff --git a/out/libcrypto-3-x64.dll b/out/libcrypto-3-x64.dll index 5b75f1161..a818e68d5 100644 Binary files a/out/libcrypto-3-x64.dll and b/out/libcrypto-3-x64.dll differ diff --git a/out/libeay32-32.dll b/out/libeay32-32.dll deleted file mode 100644 index 03eb35d46..000000000 Binary files a/out/libeay32-32.dll and /dev/null differ diff --git a/out/libeay32-64.dll b/out/libeay32-64.dll index cd605f4b0..8d34547af 100644 Binary files a/out/libeay32-64.dll and b/out/libeay32-64.dll differ diff --git a/out/libiconv-2-32.dll b/out/libiconv-2-32.dll deleted file mode 100644 index 11beb1fc5..000000000 Binary files a/out/libiconv-2-32.dll and /dev/null differ diff --git a/out/libiconv-2-64.dll b/out/libiconv-2-64.dll index 53e907700..1e990c66f 100644 Binary files a/out/libiconv-2-64.dll and b/out/libiconv-2-64.dll differ diff --git a/out/libintl-8-32.dll b/out/libintl-8-32.dll deleted file mode 100644 index 4e8daae1e..000000000 Binary files a/out/libintl-8-32.dll and /dev/null differ diff --git a/out/libintl-8-64.dll b/out/libintl-8-64.dll deleted file mode 100644 index 5d68c23e7..000000000 Binary files a/out/libintl-8-64.dll and /dev/null differ diff --git a/out/libmariadb-64.dll b/out/libmariadb-64.dll new file mode 100644 index 000000000..6efb2852a Binary files /dev/null and b/out/libmariadb-64.dll differ diff --git a/out/libmariadb32.dll b/out/libmariadb32.dll deleted file mode 100644 index b08dee210..000000000 Binary files a/out/libmariadb32.dll and /dev/null differ diff --git a/out/libmariadb64.dll b/out/libmariadb64.dll deleted file mode 100644 index c3ba5f174..000000000 Binary files a/out/libmariadb64.dll and /dev/null differ diff --git a/out/libmysql64.dll b/out/libmysql-64.dll similarity index 100% rename from out/libmysql64.dll rename to out/libmysql-64.dll diff --git a/out/libmysql-6.1-32.dll b/out/libmysql-8.4.0-64.dll similarity index 54% rename from out/libmysql-6.1-32.dll rename to out/libmysql-8.4.0-64.dll index aed0d8771..d9a2c2971 100644 Binary files a/out/libmysql-6.1-32.dll and b/out/libmysql-8.4.0-64.dll differ diff --git a/out/libmysql-9.4.0-64.dll b/out/libmysql-9.4.0-64.dll new file mode 100644 index 000000000..e65f866be Binary files /dev/null and b/out/libmysql-9.4.0-64.dll differ diff --git a/out/libmysql32.dll b/out/libmysql32.dll deleted file mode 100644 index ca36aad03..000000000 Binary files a/out/libmysql32.dll and /dev/null differ diff --git a/out/libpq-10-32.dll b/out/libpq-10-32.dll deleted file mode 100644 index b97c33d5f..000000000 Binary files a/out/libpq-10-32.dll and /dev/null differ diff --git a/out/libpq-10-64.dll b/out/libpq-10-64.dll deleted file mode 100644 index cc0bed2b9..000000000 Binary files a/out/libpq-10-64.dll and /dev/null differ diff --git a/out/libpq-12-64.dll b/out/libpq-12-64.dll deleted file mode 100644 index 14703e5f4..000000000 Binary files a/out/libpq-12-64.dll and /dev/null differ diff --git a/out/libpq-15-64.dll b/out/libpq-15-64.dll index 51ef36c57..416d68e78 100644 Binary files a/out/libpq-15-64.dll and b/out/libpq-15-64.dll differ diff --git a/out/libpq-17-64.dll b/out/libpq-17-64.dll new file mode 100644 index 000000000..c7e450172 Binary files /dev/null and b/out/libpq-17-64.dll differ diff --git a/out/libssl-1_1-32.dll b/out/libssl-1_1-32.dll deleted file mode 100644 index b47ee968e..000000000 Binary files a/out/libssl-1_1-32.dll and /dev/null differ diff --git a/out/libssl-1_1-x64.dll b/out/libssl-1_1-x64.dll deleted file mode 100644 index dd872282c..000000000 Binary files a/out/libssl-1_1-x64.dll and /dev/null differ diff --git a/out/libssl-3-x64.dll b/out/libssl-3-x64.dll index f294d31c3..e25fed2e1 100644 Binary files a/out/libssl-3-x64.dll and b/out/libssl-3-x64.dll differ diff --git a/out/libwinpthread-1-64.dll b/out/libwinpthread-1-64.dll index 1ae10ed8d..2d5ecc2eb 100644 Binary files a/out/libwinpthread-1-64.dll and b/out/libwinpthread-1-64.dll differ diff --git a/out/license.txt b/out/license.txt index 47adda3d4..66d8d2ba1 100644 --- a/out/license.txt +++ b/out/license.txt @@ -1,4 +1,4 @@ -Copyright (C)2000 - 2023 - Ansgar Becker +Copyright (C)2000 - 2025 - Ansgar Becker HeidiSQL is free. You don't have to pay for it, and you can use it any way you want. It is developed as an Open Source project under the GNU diff --git a/out/locale/en/LC_MESSAGES/default.po b/out/locale/en/LC_MESSAGES/default.po index f1b17c225..4d11cf009 100644 --- a/out/locale/en/LC_MESSAGES/default.po +++ b/out/locale/en/LC_MESSAGES/default.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: HeidiSQL\n" "POT-Creation-Date: 2012-11-05 21:40\n" -"PO-Revision-Date: 2023-12-02 14:00+0100\n" +"PO-Revision-Date: 2025-11-06 19:16+0100\n" "Last-Translator: Ansgar Becker \n" "Language-Team: English (http://www.transifex.com/projects/p/heidisql/language/en/)\n" "Language: en\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.4.1\n" +"X-Generator: Poedit 3.8\n" #. AboutBox..Caption #: about.dfm:5 @@ -315,6 +315,9 @@ msgstr "Flush tables" msgid "Databases:" msgstr "Databases:" +msgid "Encryption parameters:" +msgstr "Encryption parameters:" + #. connform..PageControlDetails..tabSettings..chkCompressed..Caption #: connections.dfm:298 msgid "Compressed client/server protocol" @@ -436,6 +439,21 @@ msgstr "SSL certificate:" msgid "SSL cipher:" msgstr "SSL cipher:" +msgid "Certificate verification" +msgstr "Certificate verification" + +msgid "No verification (insecure)" +msgstr "No verification (insecure)" + +msgid "Verify CA (insecure)" +msgstr "Verify CA (insecure)" + +msgid "Verify CA and host name identity (may fail with self-signed certs and wildcard cn)" +msgstr "Verify CA and host name identity (may fail with self-signed certs and wildcard cn)" + +msgid "You might need to lower the certificate verification in the SSL settings." +msgstr "You might need to lower the certificate verification in the SSL settings." + msgid "Path to key file" msgstr "Path to key file" @@ -1081,6 +1099,9 @@ msgstr "Input file contains local formatted numbers, e.g. 1.234,56 in Germany" msgid "Truncate destination table before import" msgstr "Truncate destination table before import" +msgid "Keep dialog open after import" +msgstr "Keep dialog open after import" + #. loaddataform..grpDuplicates..Caption #: loaddata.dfm:272 msgid "Handling of duplicate rows" @@ -2776,6 +2797,18 @@ msgstr "Close tab on doubleclick" msgid "Close tab on middleclick" msgstr "Close tab on middleclick" +msgid "Grayscale inactive tab icons" +msgstr "Grayscale inactive tab icons" + +msgid "Color icons on all tabs" +msgstr "Color icons on all tabs" + +msgid "Grayscale icons on inactive query tabs only" +msgstr "Grayscale icons on inactive query tabs only" + +msgid "Grayscale icons on every inactive tab" +msgstr "Grayscale icons on every inactive tab" + msgid "Tabs in multiple lines" msgstr "Tabs in multiple lines" @@ -3259,6 +3292,15 @@ msgstr "Expression" msgid "Virtuality" msgstr "Virtuality" +msgid "Spatial reference system" +msgstr "Spatial reference system" + +msgid "Invisible" +msgstr "Invisible" + +msgid "Hide in certain contexts" +msgstr "Hide in certain contexts" + #. frmTableEditor..PageControlMain..tabBasic..Caption #: table_editor.dfm:183 msgid "Basic" @@ -3989,6 +4031,9 @@ msgstr "Your %s is incompatible to %s, or your system is missing a dependent lib msgid "SSL parameters successfully set." msgstr "SSL parameters successfully set." +msgid "SSL parameters not fully set. Result: %d" +msgstr "SSL parameters not fully set. Result: %d" + #: dbconnection.pas:1151 msgid "Attempt to create SSH process, waiting %ds for response ..." msgstr "Attempt to create SSH process, waiting %ds for response ..." @@ -4072,6 +4117,9 @@ msgstr "Login to %s:" msgid "Connecting to %s via %s, username %s, using password: %s ..." msgstr "Connecting to %s via %s, username %s, using password: %s ..." +msgid "Connecting to %s via %s, cipher %s, using encryption key: %s ..." +msgstr "Connecting to %s via %s, cipher %s, using encryption key: %s ..." + #: dbconnection.pas:1582 msgid "Closing SSH process #%d ..." msgstr "Closing SSH process #%d ..." @@ -4283,13 +4331,6 @@ msgstr "Save new %s?" msgid "Could not open %s (%s)" msgstr "Could not open %s (%s)" -msgid "Could not open %s (%s) - trying again without SSL..." -msgstr "Could not open %s (%s) - trying again without SSL..." - -#: helpers.pas:2829 -msgid "Server did not send required \"Content-Length\" header: %s" -msgstr "Server did not send required \"Content-Length\" header: %s" - #: helpers.pas:2837 msgid "Got HTTP status %d from %s" msgstr "Got HTTP status %d from %s" @@ -4483,10 +4524,6 @@ msgstr "First %s warnings:" msgid "Show all warnings in a new query tab?" msgstr "Show all warnings in a new query tab?" -#: main.pas:2364 -msgid "The server variable %s is currently set to %d, so you won't see all warnings." -msgstr "The server variable %s is currently set to %d, so you won't see all warnings." - #: main.pas:2379 msgid "Updating query history ..." msgstr "Updating query history ..." @@ -4651,10 +4688,16 @@ msgstr "Empty %d table(s) and/or view(s)?" msgid "No stored procedure selected." msgstr "No stored procedure selected." +msgid "No table selected." +msgstr "No table selected." + #: main.pas:3285 msgid "Please select one or more stored function(s) or routine(s)." msgstr "Please select one or more stored function(s) or routine(s)." +msgid "Please select one or more table(s) or view(s)." +msgstr "Please select one or more table(s) or view(s)." + #: main.pas:3304 msgid "Parameter" msgstr "Parameter" @@ -4799,6 +4842,9 @@ msgstr "unknown" msgid "You could try the default library %s in your session settings. (Current: %s)" msgstr "You could try the default library %s in your session settings. (Current: %s)" +msgid "This is a known issue with older libraries. Try a newer %s in the session settings." +msgstr "This is a known issue with older libraries. Try a newer %s in the session settings." + #: main.pas:5261 msgid "Specify filter-value..." msgstr "Specify filter-value..." @@ -4913,6 +4959,15 @@ msgstr "Please select a non-readonly SQL editor first." msgid "No SQL editor focused. ActiveControl is %s" msgstr "No SQL editor focused. ActiveControl is %s" +msgid "No listing or tree focused. ActiveControl is %s" +msgstr "No listing or tree focused. ActiveControl is %s" + +msgid "Copy all lines from listing or tree in CSV format" +msgstr "Copy all lines from listing or tree in CSV format" + +msgid "%s: %s lines copied to clipboard" +msgstr "%s: %s lines copied to clipboard" + #: main.pas:9768 msgid "The current editor is empty." msgstr "The current editor is empty." @@ -5329,8 +5384,8 @@ msgid "Update in progress" msgstr "Update in progress" #: updatecheck.pas:264 -msgid "Downloading: %s / %s" -msgstr "Downloading: %s / %s" +msgid "Downloading: %s" +msgstr "Downloading: %s" #: usermanager.pas:435 msgid "Save modified user?" @@ -5756,13 +5811,17 @@ msgstr "The selected foreign column do not match the source columns data type an msgid "Foreign key mismatch" msgstr "Foreign key mismatch" -#. Clear data tab filter +#. Clear data tab filter and sort order msgid "Data filter for %s deleted" msgstr "Data filter for %s deleted" -#. Clear data tab filter -msgid "Clear data tab filter" -msgstr "Clear data tab filter" +#. Clear data tab filter and sort order +msgid "Sort order for %s deleted" +msgstr "Sort order for %s deleted" + +#. Clear data tab filter and sort order +msgid "Clear data tab filter and sort order" +msgstr "Clear data tab filter and sort order" msgid "Error when updating query history: %s" msgstr "Error when updating query history: %s" @@ -6648,3 +6707,63 @@ msgstr "Reset panel dimensions" msgid "Reset and fix overlapping panels in main window" msgstr "Reset and fix overlapping panels in main window" + +msgid "Warning: Given cipher scheme name \"%s\" could not be found" +msgstr "Warning: Given cipher scheme name \"%s\" could not be found" + +msgid "Warning: Configuring with cipher index %d failed" +msgstr "Warning: Configuring with cipher index %d failed" + +msgid "Warning: Failed to set cipher encryption parameter \"%s\"" +msgstr "Warning: Failed to set cipher encryption parameter \"%s\"" + +msgid "Info: Cipher encryption parameter \"%s\" set" +msgstr "Info: Cipher encryption parameter \"%s\" set" + +msgid "You have activated encryption on a probably non-encrypted database." +msgstr "You have activated encryption on a probably non-encrypted database." + +msgid "Number of rows:" +msgstr "Number of rows:" + +msgid "Amount of NULLs [percent]:" +msgstr "Amount of NULLs [percent]:" + +msgid "Generate" +msgstr "Generate" + +msgid "Generate data" +msgstr "Generate data" + +msgid "Reformatter:" +msgstr "Reformatter:" + +msgid "Always ask" +msgstr "Always ask" + +msgid "" +"File already exists: %s\n" +"\n" +"Overwrite it?" +msgstr "" +"File already exists: %s\n" +"\n" +"Overwrite it?" + +msgid "Export cancelled, file not overwritten: %s" +msgstr "Export cancelled, file not overwritten: %s" + +msgid "Discard changes?" +msgstr "Discard changes?" + +msgid "Select top %s rows" +msgstr "Select top %s rows" + +msgid "Selects the first %s rows in a new query tab" +msgstr "Selects the first %s rows in a new query tab" + +msgid "Open file after creation" +msgstr "Open file after creation" + +msgid "Source table" +msgstr "Source table" diff --git a/out/plink-0.81-64.exe b/out/plink-0.81-64.exe new file mode 100644 index 000000000..c1dbbd092 Binary files /dev/null and b/out/plink-0.81-64.exe differ diff --git a/out/plink-32.exe b/out/plink-32.exe deleted file mode 100644 index 95f91a4da..000000000 Binary files a/out/plink-32.exe and /dev/null differ diff --git a/out/plink-64.exe b/out/plink-64.exe index 3b73b55bd..11aa60d03 100644 Binary files a/out/plink-64.exe and b/out/plink-64.exe differ diff --git a/out/plugins32/auth_gssapi_client.dll b/out/plugins32/auth_gssapi_client.dll deleted file mode 100644 index 83a2acb99..000000000 Binary files a/out/plugins32/auth_gssapi_client.dll and /dev/null differ diff --git a/out/plugins32/auth_named_pipe.dll b/out/plugins32/auth_named_pipe.dll deleted file mode 100644 index 91d079d3d..000000000 Binary files a/out/plugins32/auth_named_pipe.dll and /dev/null differ diff --git a/out/plugins32/caching_sha2_password.dll b/out/plugins32/caching_sha2_password.dll deleted file mode 100644 index 262ee1d4b..000000000 Binary files a/out/plugins32/caching_sha2_password.dll and /dev/null differ diff --git a/out/plugins32/client_ed25519.dll b/out/plugins32/client_ed25519.dll deleted file mode 100644 index 6550c0af3..000000000 Binary files a/out/plugins32/client_ed25519.dll and /dev/null differ diff --git a/out/plugins32/dialog.dll b/out/plugins32/dialog.dll deleted file mode 100644 index 0037b0a2e..000000000 Binary files a/out/plugins32/dialog.dll and /dev/null differ diff --git a/out/plugins32/mysql_clear_password.dll b/out/plugins32/mysql_clear_password.dll deleted file mode 100644 index 7d29143f2..000000000 Binary files a/out/plugins32/mysql_clear_password.dll and /dev/null differ diff --git a/out/plugins32/pvio_npipe.dll b/out/plugins32/pvio_npipe.dll deleted file mode 100644 index b12f9f927..000000000 Binary files a/out/plugins32/pvio_npipe.dll and /dev/null differ diff --git a/out/plugins32/pvio_shmem.dll b/out/plugins32/pvio_shmem.dll deleted file mode 100644 index e7f51e1ed..000000000 Binary files a/out/plugins32/pvio_shmem.dll and /dev/null differ diff --git a/out/plugins32/sha256_password.dll b/out/plugins32/sha256_password.dll deleted file mode 100644 index b229ea264..000000000 Binary files a/out/plugins32/sha256_password.dll and /dev/null differ diff --git a/out/plugins64/auth_gssapi_client.dll b/out/plugins64/auth_gssapi_client.dll index 80350ea97..e09ff0aa6 100644 Binary files a/out/plugins64/auth_gssapi_client.dll and b/out/plugins64/auth_gssapi_client.dll differ diff --git a/out/plugins64/caching_sha2_password.dll b/out/plugins64/caching_sha2_password.dll index 895263288..2d8accce6 100644 Binary files a/out/plugins64/caching_sha2_password.dll and b/out/plugins64/caching_sha2_password.dll differ diff --git a/out/plugins64/client_ed25519.dll b/out/plugins64/client_ed25519.dll index dd445d431..f224a30d9 100644 Binary files a/out/plugins64/client_ed25519.dll and b/out/plugins64/client_ed25519.dll differ diff --git a/out/plugins64/dialog.dll b/out/plugins64/dialog.dll index 27dbc0a19..467d4eb65 100644 Binary files a/out/plugins64/dialog.dll and b/out/plugins64/dialog.dll differ diff --git a/out/plugins64/mysql_clear_password.dll b/out/plugins64/mysql_clear_password.dll index c614769f0..9f7fe5d25 100644 Binary files a/out/plugins64/mysql_clear_password.dll and b/out/plugins64/mysql_clear_password.dll differ diff --git a/out/plugins64/parsec.dll b/out/plugins64/parsec.dll new file mode 100644 index 000000000..14b086980 Binary files /dev/null and b/out/plugins64/parsec.dll differ diff --git a/out/plugins64/pvio_shmem.dll b/out/plugins64/pvio_shmem.dll index 8e8231968..ec3c0a81a 100644 Binary files a/out/plugins64/pvio_shmem.dll and b/out/plugins64/pvio_shmem.dll differ diff --git a/out/plugins64/sha256_password.dll b/out/plugins64/sha256_password.dll index 4959bf03c..afc5548f7 100644 Binary files a/out/plugins64/sha256_password.dll and b/out/plugins64/sha256_password.dll differ diff --git a/out/sqlite3-32.dll b/out/sqlite3-32.dll deleted file mode 100644 index c8ad18817..000000000 Binary files a/out/sqlite3-32.dll and /dev/null differ diff --git a/out/sqlite3-64.dll b/out/sqlite3-64.dll index 1239c3520..ae8492c56 100644 Binary files a/out/sqlite3-64.dll and b/out/sqlite3-64.dll differ diff --git a/out/sqlite3mc-64.dll b/out/sqlite3mc-64.dll new file mode 100644 index 000000000..9affcfb99 Binary files /dev/null and b/out/sqlite3mc-64.dll differ diff --git a/out/ssleay32-32.dll b/out/ssleay32-32.dll deleted file mode 100644 index a8e34648f..000000000 Binary files a/out/ssleay32-32.dll and /dev/null differ diff --git a/out/ssleay32-64.dll b/out/ssleay32-64.dll index f4b5c75be..44f79428e 100644 Binary files a/out/ssleay32-64.dll and b/out/ssleay32-64.dll differ diff --git a/packages/Delphi11.2/heidisql.dpr b/packages/Delphi12.1/heidisql.dpr similarity index 96% rename from packages/Delphi11.2/heidisql.dpr rename to packages/Delphi12.1/heidisql.dpr index 418f51321..b9d721e61 100644 --- a/packages/Delphi11.2/heidisql.dpr +++ b/packages/Delphi12.1/heidisql.dpr @@ -97,6 +97,9 @@ begin // Issue #3064: Ignore TFont, so "Default" on mainform for WinXP users does not get broken. gnugettext.TP_GlobalIgnoreClass(TFont); + // Enable padding in customized tooltips + HintWindowClass := TExtHintWindow; + Application.Initialize; Application.Title := APPNAME; Application.UpdateFormatSettings := False; diff --git a/packages/Delphi11.2/heidisql.dproj b/packages/Delphi12.1/heidisql.dproj similarity index 88% rename from packages/Delphi11.2/heidisql.dproj rename to packages/Delphi12.1/heidisql.dproj index 3843d8e1a..49289f45c 100644 --- a/packages/Delphi11.2/heidisql.dproj +++ b/packages/Delphi12.1/heidisql.dproj @@ -7,8 +7,9 @@ 3 Application VCL - 19.5 + 20.1 Win64 + heidisql true @@ -77,6 +78,7 @@ Vcl;System;Winapi;System.Win;Data;$(DCC_Namespace) false false + false true @@ -440,6 +442,16 @@ 1 + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + res\values @@ -460,6 +472,66 @@ 1 + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + res\values @@ -470,6 +542,16 @@ 1 + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + res\drawable @@ -640,6 +722,56 @@ 1 + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + 1 @@ -770,177 +902,201 @@ 0 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + ..\ 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + ..\ + 1 + + + ..\ 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + + Contents 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + Contents + 1 + + + Contents 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + + Contents\Resources 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + Contents\Resources + 1 + + + Contents\Resources 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + library\lib\armeabi-v7a 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + library\lib\arm64-v8a + 1 + + 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Contents\MacOS 1 - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Contents\MacOS 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Contents\MacOS 1 + + 0 + - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + library\lib\armeabi-v7a 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + + 1 + + + 1 + + 1 - + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1 - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1 - + + + ..\ + 1 + - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + ..\ 1 - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + ..\ 1 - + + + 1 + - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 + ..\$(PROJECTNAME).launchscreen + 64 - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 + ..\$(PROJECTNAME).launchscreen + 64 - + + + 1 + - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + Assets 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Assets 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + Assets 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Assets 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 @@ -950,7 +1106,7 @@ 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 @@ -960,7 +1116,7 @@ 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 @@ -970,194 +1126,173 @@ 1 - - + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - + - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - - ..\ - 1 - + - ..\ + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - ..\ + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + - ..\$(PROJECTNAME).launchscreen - 64 + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 - ..\$(PROJECTNAME).launchscreen - 64 - - - - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - ..\ - 1 - - - ..\ + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - ..\ + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - - Contents - 1 - - - Contents + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - Contents + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - - Contents\Resources - 1 - - - Contents\Resources + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - Contents\Resources + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - 1 - - - Contents\MacOS + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - Contents\MacOS + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - Contents\MacOS + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - 0 - - - - - library\lib\armeabi-v7a + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - Assets + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - Assets + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - Assets + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - Assets + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 @@ -1173,6 +1308,7 @@ + 12 diff --git a/packages/Delphi11.2/heidisql.groupproj b/packages/Delphi12.1/heidisql.groupproj similarity index 88% rename from packages/Delphi11.2/heidisql.groupproj rename to packages/Delphi12.1/heidisql.groupproj index 8541c5f98..c86a77c88 100644 --- a/packages/Delphi11.2/heidisql.groupproj +++ b/packages/Delphi12.1/heidisql.groupproj @@ -3,10 +3,10 @@ {C4296A31-CCFB-4D2F-8BEC-26CD630E9987} - + - + @@ -30,22 +30,22 @@ - + - + - + - + - + - + diff --git a/packages/Delphi11.2/heidisql.mes b/packages/Delphi12.1/heidisql.mes similarity index 100% rename from packages/Delphi11.2/heidisql.mes rename to packages/Delphi12.1/heidisql.mes diff --git a/packages/Delphi11.1/heidisql.dpr b/packages/Delphi12.3/heidisql.dpr similarity index 89% rename from packages/Delphi11.1/heidisql.dpr rename to packages/Delphi12.3/heidisql.dpr index a37910c4d..f25ac703e 100644 --- a/packages/Delphi11.1/heidisql.dpr +++ b/packages/Delphi12.3/heidisql.dpr @@ -2,10 +2,12 @@ program heidisql; uses madExcept, - Forms, - SysUtils, - Dialogs, - Windows, + Vcl.Forms, + System.SysUtils, + Vcl.Dialogs, + Vcl.Controls, + Vcl.ComCtrls, + Winapi.Windows, main in '..\..\source\main.pas' {MainForm}, about in '..\..\source\about.pas' {AboutBox}, connections in '..\..\source\connections.pas' {connform}, @@ -55,14 +57,16 @@ uses csv_detector in '..\..\source\csv_detector.pas' {frmCsvDetector}, generic_types in '..\..\source\generic_types.pas', customize_highlighter in '..\..\source\customize_highlighter.pas' {frmCustomizeHighlighter}, - Xml.VerySimple in '..\..\source\Xml.VerySimple.pas'; + Xml.VerySimple in '..\..\source\Xml.VerySimple.pas', + Sequal.Suggest in '..\..\source\Sequal.Suggest.pas' {SequalSuggestForm}, + reformatter in '..\..\source\reformatter.pas' {frmReformatter}; {.$R *.RES} {$R ..\..\res\icon.RES} {$R ..\..\res\icon-question.RES} {$R ..\..\res\version.RES} {$R ..\..\res\manifest.RES} -{$R ..\..\res\updater.RES} +{$IFDEF CPUX64}{$R ..\..\res\updater.RES}{$ENDIF} {$R ..\..\res\styles.RES} var @@ -93,6 +97,10 @@ begin // First time translation via dxgettext. // Issue #3064: Ignore TFont, so "Default" on mainform for WinXP users does not get broken. gnugettext.TP_GlobalIgnoreClass(TFont); + gnugettext.TP_GlobalIgnoreClass(TComboBoxEx); + + // Enable padding in customized tooltips + HintWindowClass := TExtHintWindow; Application.Initialize; Application.Title := APPNAME; @@ -104,7 +112,6 @@ begin if TStyleManager.ActiveStyle.Name <> WantedStyle then begin AppSettings.WriteString(asTheme, TStyleManager.ActiveStyle.Name); end; - Application.CreateForm(TMainForm, MainForm); MainForm.AfterFormCreate; Application.OnDeactivate := MainForm.ApplicationDeActivate; diff --git a/packages/Delphi11.1/heidisql.dproj b/packages/Delphi12.3/heidisql.dproj similarity index 83% rename from packages/Delphi11.1/heidisql.dproj rename to packages/Delphi12.3/heidisql.dproj index 4e7677836..a725978f6 100644 --- a/packages/Delphi11.1/heidisql.dproj +++ b/packages/Delphi12.3/heidisql.dproj @@ -3,12 +3,13 @@ {32493ED6-4F48-45D7-9D50-E4FA13F59063} heidisql.dpr True - Release + Debug 3 Application VCL - 19.4 + 20.3 Win64 + heidisql true @@ -45,12 +46,6 @@ Base true - - true - Cfg_2 - true - true - true Cfg_2 @@ -72,7 +67,6 @@ $(BDS)\bin\default_app.manifest false false - Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;Winapi;FMX.Canvas.GPU;System.Win;Data.Win;$(DCC_Namespace) false 00400000 CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName=;CFBundleDisplayName=;UIDeviceFamily=;CFBundleIdentifier=;CFBundleVersion=;CFBundlePackageType=;CFBundleSignature=;CFBundleAllowMixedLocalizations=;UISupportedInterfaceOrientations=;CFBundleExecutable=;CFBundleResourceSpecification=;LSRequiresIPhoneOS=;CFBundleInfoDictionaryVersion=;CFBundleDevelopmentRegion=;package=;label=;versionCode=;versionName=;persistent=;restoreAnyVersion=;installLocation=;largeHeap=;theme= @@ -81,17 +75,18 @@ false 1033 heidisql - Windows10|VCLSTYLE|$(BDSCOMMONDIR)\Styles\Windows10.vsf + Vcl;System;Winapi;System.Win;Data;$(DCC_Namespace) + false + false + false - Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) true CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png - Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) true $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png @@ -107,23 +102,27 @@ true CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) false + Debug + madExcept;$(DCC_Define) + 3 + 2 + true true CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + Debug madExcept;$(DCC_Define) + 2 + 3 + none + true true DEBUG;$(DCC_Define) false - - - true - Cfg_2 - true - true - Debug + true None @@ -139,7 +138,6 @@ true CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) 3 - true Debug PerMonitorV2 madExcept;$(DCC_Define) @@ -259,9 +257,16 @@

frmCustomizeHighlighter - dfm
+ +
SequalSuggestForm
+ dfm +
+ +
frmReformatter
+ dfm +
Base @@ -334,34 +339,21 @@ - Microsoft Office 2000 Beispiele für gekapselte Komponenten für Automatisierungsserver - Microsoft Office XP Beispiele für gekapselte Komponenten für Automation Server + Microsoft Office 2000 Beispiele für gekapselte Komponenten für Automatisierungsserver + Microsoft Office XP Beispiele für gekapselte Komponenten für Automation Server - False True True False - - - - true - - - - - heidisql.exe - true - - - - - .\ - true - - + + + + + + 1 @@ -374,26 +366,6 @@ 0 - - - classes - 64 - - - classes - 64 - - - - - classes - 1 - - - classes - 1 - - res\xml @@ -404,12 +376,6 @@ 1 - - - library\lib\armeabi-v7a - 1 - - library\lib\armeabi @@ -462,6 +428,16 @@ 1 + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + res\values @@ -482,6 +458,76 @@ 1 + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\values-v35 + 1 + + + res\values-v35 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + res\values @@ -492,6 +538,16 @@ 1 + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + res\drawable @@ -662,6 +718,56 @@ 1 + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + 1 @@ -703,7 +809,7 @@ 1 .dylib - + 1 .dylib @@ -736,7 +842,7 @@ 1 .dylib - + 1 .dylib @@ -773,7 +879,7 @@ 0 - + 0 @@ -792,382 +898,397 @@ 0 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + + ..\ 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + ..\ 1 - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + ..\ 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + + + Contents 1 - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Contents 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Contents 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + Contents\Resources 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Contents\Resources 1 - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Contents\Resources 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + + library\lib\armeabi-v7a 1 - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + library\lib\arm64-v8a 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + Contents\MacOS 1 + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + + library\lib\armeabi-v7a 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + + + 1 + + + 1 + + 1 - + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1 - + + + ..\ + 1 + - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + ..\ 1 - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + + ..\ 1 - + + + 1 + - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 - + - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + 1 + + 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + Assets 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Assets 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + + Assets 1 - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + + Assets 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - + - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - - - ..\ + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 + + - ..\ + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - - + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + - ..\$(PROJECTNAME).launchscreen - 64 - - - ..\$(PROJECTNAME).launchscreen - 64 + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 - - - + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - ..\ - 1 - - - ..\ + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - ..\ + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - Contents - 1 - - - Contents + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - Contents + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - - Contents\Resources - 1 - - - Contents\Resources + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - Contents\Resources + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1 - + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - Contents\MacOS + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - Contents\MacOS + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - Contents\MacOS + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - 0 - - - - library\lib\armeabi-v7a + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - Assets + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - Assets + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - - Assets + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 - - Assets + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1 @@ -1175,6 +1296,7 @@ + @@ -1182,6 +1304,7 @@ + 12 diff --git a/packages/Delphi11.1/heidisql.groupproj b/packages/Delphi12.3/heidisql.groupproj similarity index 82% rename from packages/Delphi11.1/heidisql.groupproj rename to packages/Delphi12.3/heidisql.groupproj index a51586394..ef3f2fbb5 100644 --- a/packages/Delphi11.1/heidisql.groupproj +++ b/packages/Delphi12.3/heidisql.groupproj @@ -3,16 +3,16 @@ {C4296A31-CCFB-4D2F-8BEC-26CD630E9987}
- + - + - + - + @@ -30,40 +30,40 @@ - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/packages/Delphi11.1/heidisql.mes b/packages/Delphi12.3/heidisql.mes similarity index 97% rename from packages/Delphi11.1/heidisql.mes rename to packages/Delphi12.3/heidisql.mes index 52176f0dc..cf4d3b4fd 100644 --- a/packages/Delphi11.1/heidisql.mes +++ b/packages/Delphi12.3/heidisql.mes @@ -29,7 +29,7 @@ SmtpPort=0 SmtpAccount= SmtpPassword= HttpServer=www.heidisql.com/bugreport.php -HttpPort=443 +HttpPort=0 HttpAccount= HttpPassword= BugReportFile=bugreport.txt diff --git a/readme.md b/readme.md index 2a7db80c1..e996fd965 100644 --- a/readme.md +++ b/readme.md @@ -6,27 +6,47 @@ # HeidiSQL HeidiSQL is a graphical interface for managing [MariaDB](http://www.mariadb.org/) or [MySQL](http://www.mysql.com/) servers, [Microsoft SQL databases](http://www.microsoft.com/sql/), [PostgreSQL](http://www.postgresql.org/), [SQLite](https://www.sqlite.org/), [Interbase](https://www.embarcadero.com/de/products/interbase) or [Firebird](https://firebirdsql.org/). "Heidi" lets you browse and edit data, create and edit tables, views, procedures, triggers and scheduled events. Also, you can export structure and data, either to SQL file, clipboard or to other servers. Read about [features](https://www.heidisql.com/#featurelist) or see some [screenshots](https://www.heidisql.com/screenshots.php). -# Need help? +### Need help? Look at [the online help page](https://www.heidisql.com/help.php) to learn how to use HeidiSQL. The [forum](https://www.heidisql.com/forum.php) is meant to ask questions. The [issue tracker](https://github.com/HeidiSQL/HeidiSQL/issues) is the place to report bugs or request new features. -# Building -Delphi 11.1 is required for building HeidiSQL. Older Delphi versions will most likely fail; newer Delphi versions may work or fail. Unfortunately, Lazarus or one +### Building +For compiling on platforms other than Windows, look at the [`lazarus`](https://github.com/HeidiSQL/HeidiSQL/tree/lazarus) branch. + +Delphi 12.1 is required for building HeidiSQL for Windows. Older Delphi versions will most likely fail; newer Delphi versions may work or fail. Unfortunately, Lazarus or one of the other free compilers cannot currently compile HeidiSQL. Once Delphi is installed, you need to load the SynEdit project from the components folder. Build both run-time and design-time packages. Install the -design-time package. Do the same for the VirtualTree component project, and install madExcept. +design-time package. Do the same for the VirtualTree component project. + +Second you need install [madExcept](http://madshi.net/madCollection.exe). + +Third compile *.rc files: + +| folder | file | command | +| ------ | ------ | ------ | +|HeidiSQL/source/vcl-styles-utils |AwesomeFont.RC| brcc32 AwesomeFont.RC| +|HeidiSQL/res| icon.rc | cgrc icon.rc | +|HeidiSQL/res| icon-question.rc | brcc32 icon-question.rc | +|HeidiSQL/res| version.rc | brcc32 version.rc | +|HeidiSQL/res| manifest.rc | manifest.rc | +|HeidiSQL/res| styles.rc | brcc32 styles.rc | +|HeidiSQL/res| updater.rc | brcc32 updater.rc | +> if updater.rc and updater.exe are not exists. you can copy them from updater64.rc and updater64.exe. Afterwards, load the HeidiSQL project from the packages folder. -# Translation +### Translation If you'd like to contribute by translating HeidiSQL into your mother tongue, you need to register at -[Transifex](https://www.transifex.com/heidisql/heidisql/), and join an existing language or request a +[Transifex](https://explore.transifex.com/heidisql/heidisql/), and join an existing language or request a new one. -# Contributing to HeidiSQL +### Contributing to HeidiSQL * Pull requests will only be accepted for bugfixes. No new features please. * Please mention a ticket id in your pull request. If there is no ticket for that particular bug yet, go and create an issue request first, and fill out all fields of the issue template. * To become a developer member, ask Ansgar via email (see https://www.heidisql.com/imprint.php for email address) -# Icons8 copyright +### Icons8 copyright Icons added in January 2019 into a `TImageCollection` component are copyright by [Icons8](https://icons8.com). Used with a special permission from Icons8 given to Ansgar for this project only. Do not copy them for anything else other than building HeidiSQL. + +[![Embarcadero logo.](https://www.heidisql.com/images/made-with-delphi.png)](https://www.embarcadero.com/de/case-study/heidisql-case-study) + diff --git a/res/icons/key_vector.png b/res/icons/key_vector.png new file mode 100644 index 000000000..83ae5ab03 Binary files /dev/null and b/res/icons/key_vector.png differ diff --git a/res/manifest.xml b/res/manifest.xml index 36996eaa2..b315924c5 100644 --- a/res/manifest.xml +++ b/res/manifest.xml @@ -49,4 +49,7 @@ + + + \ No newline at end of file diff --git a/res/styles/Glow.vsf b/res/styles/Glow.vsf index 9d35e00d3..6acafc775 100644 Binary files a/res/styles/Glow.vsf and b/res/styles/Glow.vsf differ diff --git a/res/version.rc b/res/version.rc index e61d40374..a60b9a1dc 100644 --- a/res/version.rc +++ b/res/version.rc @@ -1,5 +1,5 @@ 1 VERSIONINFO - FILEVERSION 12,6,0,0 + FILEVERSION 12,17,0,0 FILEOS VOS__WINDOWS32 FILETYPE VFT_APP BEGIN diff --git a/source/about.dfm b/source/about.dfm index 6eac6df96..79659877c 100644 --- a/source/about.dfm +++ b/source/about.dfm @@ -3,7 +3,7 @@ object AboutBox: TAboutBox Top = 105 BorderStyle = bsDialog Caption = 'About' - ClientHeight = 327 + ClientHeight = 371 ClientWidth = 471 Color = clBtnFace Font.Charset = DEFAULT_CHARSET @@ -16,7 +16,7 @@ object AboutBox: TAboutBox OnShow = FormShow DesignSize = ( 471 - 327) + 371) TextHeight = 14 object lblAppName: TLabel Left = 117 @@ -40,7 +40,7 @@ object AboutBox: TAboutBox end object lblAppCompiled: TLabel Left = 117 - Top = 62 + Top = 63 Width = 82 Height = 14 Caption = 'lblAppCompiled' @@ -264,14 +264,14 @@ object AboutBox: TAboutBox end object lblDonated: TLabel Left = 117 - Top = 153 + Top = 171 Width = 238 Height = 14 Caption = 'I have donated per following email address:' end object lblEnvironment: TLabel Left = 117 - Top = 81 + Top = 83 Width = 80 Height = 14 Caption = 'lblEnvironment' @@ -279,9 +279,9 @@ object AboutBox: TAboutBox end object lnklblWebpage: TLinkLabel Left = 117 - Top = 100 - Width = 83 - Height = 19 + Top = 103 + Width = 104 + Height = 24 Caption = 'lnklblWebpage' TabOrder = 0 UseVisualStyle = True @@ -289,9 +289,9 @@ object AboutBox: TAboutBox end object lnklblCredits: TLinkLabel Left = 117 - Top = 119 - Width = 41 - Height = 19 + Top = 123 + Width = 50 + Height = 24 Caption = 'Credits' TabOrder = 1 UseVisualStyle = True @@ -299,7 +299,7 @@ object AboutBox: TAboutBox end object btnClose: TButton Left = 353 - Top = 294 + Top = 338 Width = 100 Height = 25 Anchors = [akRight, akBottom] @@ -311,7 +311,7 @@ object AboutBox: TAboutBox end object btnUpdateCheck: TButton Left = 117 - Top = 294 + Top = 338 Width = 230 Height = 25 Action = MainForm.actUpdateCheck @@ -321,7 +321,7 @@ object AboutBox: TAboutBox end object editDonated: TEdit Left = 117 - Top = 172 + Top = 190 Width = 180 Height = 22 TabOrder = 2 @@ -331,7 +331,7 @@ object AboutBox: TAboutBox end object btnDonatedOK: TButton Left = 303 - Top = 171 + Top = 189 Width = 64 Height = 25 Caption = 'OK' @@ -340,7 +340,7 @@ object AboutBox: TAboutBox end object btnDonate: TButton Left = 117 - Top = 200 + Top = 218 Width = 336 Height = 74 Hint = @@ -354,6 +354,16 @@ object AboutBox: TAboutBox Style = bsCommandLink TabOrder = 4 end + object lnklblCompiler: TLinkLabel + Left = 246 + Top = 63 + Width = 101 + Height = 24 + Caption = 'lnklblCompiler' + TabOrder = 7 + UseVisualStyle = True + OnLinkClick = lnklblWebpageLinkClick + end object popupLabels: TPopupMenu Images = MainForm.VirtualImageListMain Left = 32 diff --git a/source/about.pas b/source/about.pas index b15dc0489..f72b4bc0c 100644 --- a/source/about.pas +++ b/source/about.pas @@ -27,6 +27,7 @@ TAboutBox = class(TExtForm) menuCopyLabel: TMenuItem; lblEnvironment: TLabel; btnDonate: TButton; + lnklblCompiler: TLinkLabel; procedure OpenURL(Sender: TObject); procedure FormShow(Sender: TObject); procedure editDonatedEnter(Sender: TObject); @@ -39,6 +40,7 @@ TAboutBox = class(TExtForm) procedure menuCopyLabelClick(Sender: TObject); private { Private declarations } + function GetDelphiVersion: String; public { Public declarations } end; @@ -120,7 +122,10 @@ procedure TAboutBox.FormShow(Sender: TObject); Caption := f_('About %s', [APPNAME]); lblAppName.Caption := APPNAME; lblAppVersion.Caption := _('Version') + ' ' + Mainform.AppVersion + ' (' + IntToStr(GetExecutableBits) + ' Bit)'; - lblAppCompiled.Caption := _('Compiled on:') + ' ' + DateTimeToStr(GetImageLinkTimeStamp(Application.ExeName)); + lblAppCompiled.Caption := _('Compiled on:') + ' ' + DateTimeToStr(GetImageLinkTimeStamp(Application.ExeName)) + ' with'; + lnklblCompiler.Top := lblAppCompiled.Top; + lnklblCompiler.Left := lblAppCompiled.Left + lblAppCompiled.Width + Canvas.TextWidth(' '); + lnklblCompiler.Caption := ''+GetDelphiVersion+''; lnklblWebpage.Caption := ''+APPDOMAIN+''; lnklblCredits.Caption := ''+lnklblCredits.Caption+''; ImageHeidisql.Hint := APPDOMAIN+'?place='+EncodeURLParam(ImageHeidisql.Name); @@ -158,5 +163,21 @@ procedure TAboutBox.lnklblWebpageLinkClick(Sender: TObject; const Link: string; ShellExec(Link); end; +function TAboutBox.GetDelphiVersion: string; +begin + {$IF Defined(VER360)} + // Oldest/first official version where this gets used + Result := '12'; + {$ELSEIF Defined(VER350)} + Result := '11'; + {$ELSEIF Defined(VER340)} + Result := '10.4'; + {$ELSE} + Result := '10.3 or older'; + {$ENDIF} + + Result := 'Delphi ' + Result; +end; + end. diff --git a/source/apphelpers.pas b/source/apphelpers.pas index 7c9d6686a..35e19ab56 100644 --- a/source/apphelpers.pas +++ b/source/apphelpers.pas @@ -10,11 +10,11 @@ interface uses System.Classes, System.SysUtils, Vcl.Graphics, Vcl.GraphUtil, Vcl.ClipBrd, Vcl.Dialogs, Vcl.Forms, Vcl.Controls, Winapi.ShellApi, - Winapi.Windows, Winapi.ShlObj, Winapi.ActiveX, VirtualTrees, VirtualTrees.Types, SynRegExpr, Winapi.Messages, System.Math, + Winapi.Windows, Winapi.ShlObj, Winapi.ActiveX, VirtualTrees, VirtualTrees.BaseTree, VirtualTrees.Types, SynRegExpr, Winapi.Messages, System.Math, System.Win.Registry, System.DateUtils, System.Generics.Collections, System.Contnrs, System.StrUtils, System.AnsiStrings, Winapi.TlHelp32, System.Types, dbconnection, dbstructures, dbstructures.mysql, SynMemo, Vcl.Menus, Winapi.WinInet, gnugettext, Vcl.Themes, System.Character, Vcl.ImgList, System.UITypes, Vcl.ActnList, Winapi.WinSock, System.IOUtils, Vcl.StdCtrls, Vcl.ComCtrls, - Winapi.CommCtrl, Winapi.KnownFolders, SynUnicode, SynEdit; + Winapi.CommCtrl, Winapi.KnownFolders, SynUnicode, SynEdit, System.IniFiles; type @@ -76,10 +76,14 @@ TSQLSentence = class(TObject) TSQLBatch = class(TObjectList) private FSQL: String; + FQuotes: THashedStringList; + FEscape: Char; procedure SetSQL(Value: String); function GetSize: Integer; function GetSQLWithoutComments: String; overload; public + constructor Create(NetTypeGroup: TNetTypeGroup); + destructor Destroy; override; class function GetSQLWithoutComments(FullSQL: String): String; overload; property Size: Integer read GetSize; property SQL: String read FSQL write SetSQL; @@ -93,7 +97,6 @@ THttpDownload = class(TObject) FURL: String; FLastContent: String; FBytesRead: Integer; - FContentLength: Integer; FTimeOut: Cardinal; FOnProgress: TNotifyEvent; public @@ -103,7 +106,6 @@ THttpDownload = class(TObject) 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; @@ -170,6 +172,8 @@ TWinControlHelper = class helper for TWinControl procedure TrySetFocus; end; + //TSimpleKeyValuePairs = TDictionary; + TAppSettingDataType = (adInt, adBool, adString); TAppSettingIndex = (asHiddenColumns, asFilter, asSort, asDisplayedColumnsSorted, asLastSessions, asLastActiveSession, asAutoReconnect, asRestoreLastUsedDB, asLastUsedDB, asTreeBackground, asIgnoreDatabasePattern, asLogFileDdl, asLogFileDml, asLogFilePath, @@ -184,16 +188,16 @@ TWinControlHelper = class helper for TWinControl 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, + asUser, asPassword, asCleartextPluginEnabled, asForceUnicode, asWindowsAuth, asLoginPrompt, asPort, asLibrary, asAllProviders, asSSHtunnelActive, asPlinkExecutable, asSshExecutable, asSSHtunnelHost, asSSHtunnelHostPort, asSSHtunnelPort, asSSHtunnelUser, asSSHtunnelPassword, asSSHtunnelTimeout, asSSHtunnelPrivateKey, asSSLActive, asSSLKey, - asSSLCert, asSSLCA, asSSLCipher, asSSLWarnUnused, asNetType, asCompressed, asLocalTimeZone, asQueryTimeout, asKeepAlive, + 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, + asExportSQLDatabase, asExportSQLServerDatabase, asExportSQLOutput, asExportSQLAddComments, asExportSQLTransactions, asExportSQLRemoveAutoIncrement, asExportSQLRemoveDefiner, asGridExportWindowWidth, asGridExportWindowHeight, asGridExportOutputCopy, asGridExportOutputFile, asGridExportFilename, asGridExportRecentFiles, asGridExportEncoding, asGridExportFormat, asGridExportSelection, - asGridExportColumnNames, asGridExportIncludeAutoInc, asGridExportIncludeQuery, asGridExportRemoveLinebreaks, + asGridExportColumnNames, asGridExportIncludeAutoInc, asGridExportFocusedColumnOnly, asGridExportIncludeQuery, asGridExportRemoveLinebreaks, asGridExportOpenFile, asGridExportSeparator, asGridExportEncloser, asGridExportTerminator, asGridExportNull, asGridExportClpColumnNames, asGridExportClpIncludeAutoInc, asGridExportClpRemoveLinebreaks, @@ -201,18 +205,19 @@ TWinControlHelper = class helper for TWinControl asCSVImportSeparator, asCSVImportEncloser, asCSVImportTerminator, asCSVImportFieldEscaper, asCSVImportWindowWidth, asCSVImportWindowHeight, asCSVImportFilename, asCSVImportFieldsEnclosedOptionally, asCSVImportIgnoreLines, asCSVImportLowPriority, asCSVImportLocalNumbers, - asCSVImportDuplicateHandling, asCSVImportParseMethod, + 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, + asSessionManagerListWidth, asSessionManagerListFoldersAtTop, 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, asWarnUnsafeUpdates, asQueryWarningsMessage, asQueryGridLongSortRowNum, + asPromptSaveFileOnTabClose, asRestoreTabs, asTabCloseOnDoubleClick, asTabCloseOnMiddleClick, asTabsInMultipleLines, asTabIconsGrayscaleMode, + asWarnUnsafeUpdates, asQueryGridLongSortRowNum, asCompletionProposal, asCompletionProposalInterval, asCompletionProposalSearchOnMid, asCompletionProposalWidth, asCompletionProposalNbLinesInWindow, asAutoUppercase, asTabsToSpaces, asFilterPanel, asAllowMultipleInstances, asFindDialogSearchHistory, asGUIFontName, asGUIFontSize, asTheme, asIconPack, asWebSearchBaseUrl, @@ -231,7 +236,8 @@ TWinControlHelper = class helper for TWinControl asThemePreviewWidth, asThemePreviewHeight, asThemePreviewTop, asThemePreviewLeft, asCreateDbCollation, asRealTrailingZeros, asSequalSuggestWindowWidth, asSequalSuggestWindowHeight, asSequalSuggestPrompt, asSequalSuggestRecentPrompts, - asReformatter, asAlwaysGenerateFilter, + asReformatter, asReformatterNoDialog, asAlwaysGenerateFilter, asDisplayReverseForeignKeys, + asGenerateDataNumRows, asGenerateDataNullAmount, asWebOnceAction, asDisplayLogPanel, asDisplayTreeFilters, asUnused); TAppSetting = record Name: String; @@ -318,6 +324,9 @@ TAppSettings = class(TObject) 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 isUnicode(str: String): Boolean; + function encryptUnicode(str: String): String; + function decryptUnicode(str: String): String; function encrypt(str: String): String; function decrypt(str: String): String; function HTMLSpecialChars(str: String): String; @@ -333,6 +342,7 @@ TAppSettings = class(TObject) 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; @@ -350,7 +360,8 @@ TAppSettings = class(TObject) 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); + function GetAppDir: 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; @@ -390,6 +401,7 @@ TAppSettings = class(TObject) function GetImageLinkTimeStamp(const FileName: string): TDateTime; function IsEmpty(Str: String): Boolean; function IsNotEmpty(Str: String): Boolean; + function IfEmpty(Str: String; WhenEmpty: String): String; 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; @@ -403,6 +415,7 @@ TAppSettings = class(TObject) function GetSystemImageList: TImageList; function GetSystemImageIndex(Filename: String): Integer; function GetExecutableBits: Byte; + procedure GetExecutableVersion(FileName: String; var MajorVer, MinorVer, ReleaseVer, RevisionVer: Word); procedure Help(Sender: TObject; Anchor: String); function PortOpen(Port: Word): Boolean; function IsValidFilePath(FilePath: String): Boolean; @@ -410,7 +423,7 @@ TAppSettings = class(TObject) 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 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; @@ -420,6 +433,8 @@ TAppSettings = class(TObject) procedure FindComponentInstances(BaseForm: TComponent; ClassType: TClass; var List: TObjectList); function WebColorStrToColorDef(WebColor: string; Default: TColor): TColor; function UserAgent(OwnerComponent: TComponent): String; + function CodeIndent(Steps: Integer=1): String; + function EscapeHotkeyPrefix(Text: String): String; var AppSettings: TAppSettings; @@ -526,6 +541,77 @@ function StrEllipsis(const S: String; MaxLen: Integer; FromLeft: Boolean=True): +{*** + Check if string is Unicode + + @param string String to check + @return boolean +} +function isUnicode(str: String): Boolean; +var i: integer; +begin + result := false; + for i := 1 to length(str) do begin + result := ord(str[i]) > 255; + if result then exit; + end; +end; + + +{*** + Password-encryption, used to store session-passwords in registry + Unicode (UTF-16) version, support up to 0xFFFF + + @param string Text to encrypt + @return string Encrypted Text +} +function encryptUnicode(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) mod 65536; + h := IntToHex(nr, 4); // 4 hex-symbols + result := result + h; + end; + // Adding Unicode flag + result := result + IntToStr(salt) + '0'; +end; + + +{*** + Password-decryption, used to restore session-passwords from registry + Unicode (UTF-16) version, support up to 0xFFFF + + @param string Text to decrypt + @return string Decrypted Text +} +function decryptUnicode(str: String): String; +var + j, salt, nr: integer; +begin + result := ''; + if str = '' then exit; + salt := StrToIntDef(str[length(str)], -1); + + // Salt is NAN + if salt < 0 then exit; + + j := 1; + while j < length(str) do begin + nr := StrToInt('$' + copy(str, j, 4)) - salt; + if nr < 0 then + nr := nr + 65536; + result := result + chr(nr); + inc(j, 4); + end; +end; + + {*** Password-encryption, used to store session-passwords in registry @@ -537,6 +623,11 @@ function encrypt(str: String) : String; i, salt, nr : integer; h : String; begin + if isUnicode(str) then begin + result := encryptUnicode(str); + exit; + end; + randomize(); result := ''; salt := random(9) + 1; @@ -553,7 +644,6 @@ function encrypt(str: String) : String; end; - {*** Password-decryption, used to restore session-passwords from registry @@ -566,9 +656,20 @@ function decrypt(str: String) : String; begin result := ''; if str = '' then exit; + salt := StrToIntDef(str[length(str)], -1); + + // Salt is NAN - error + if salt < 0 then exit; + + // Salt is Unicode flag - Unicode logic + if salt = 0 then begin + // Removing Unicode flag + result := decryptUnicode(copy(str, 1, length(str) - 1)); + exit; + end; + + // Salt is... salt - ANSI logic 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 @@ -610,10 +711,13 @@ function EncodeURLParam(const Value: String): String; } procedure StreamWrite(S: TStream; Text: String = ''); var - utf8: AnsiString; + utf8: UTF8String; + L: Integer; begin utf8 := Utf8Encode(Text); - S.Write(utf8[1], Length(utf8)); + L := Length(utf8); + if L > 0 then + S.WriteBuffer(utf8[1], L); end; @@ -827,6 +931,12 @@ function fixNewlines(txt: String): String; 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 @@ -1195,14 +1305,22 @@ function GetTempDir: String; end; +function GetAppDir: String; +begin + Result := ExtractFilePath(Application.ExeName); +end; + {** Save a textfile with unicode } -procedure SaveUnicodeFile(Filename: String; Text: String); +procedure SaveUnicodeFile(Filename: String; Text: String; Encoding: TEncoding); var Writer: TStreamWriter; begin - Writer := TStreamWriter.Create(Filename, False, UTF8NoBOMEncoding); + // Encoding may be nil when previously loaded via auto-detection + if not Assigned(Encoding) then + Encoding := UTF8NoBOMEncoding; + Writer := TStreamWriter.Create(Filename, False, Encoding); Writer.Write(Text); Writer.Free; end; @@ -1217,6 +1335,9 @@ procedure OpenTextFile(const Filename: String; out Stream: TFileStream; var Enco Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyNone); if Encoding = nil then Encoding := DetectEncoding(Stream); + // For a 0-bytes file, override the encoding to one without BOM + if _GetFileSize(Filename) < Length(Encoding.GetPreamble) then + Encoding := UTF8NoBOMEncoding; // If the file contains a BOM, advance the stream's position BomLen := 0; if Length(Encoding.GetPreamble) > 0 then begin @@ -1403,15 +1524,19 @@ procedure FixVT(VT: TVirtualStringTree; MultiLineCount: Word=1); SingleLineHeight := GetTextHeight(VT.Font) + 7; // Multiline nodes? VT.DefaultNodeHeight := SingleLineHeight * MultiLineCount; + VT.Header.MinHeight := SingleLineHeight; 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; + VT.TextMargin := 6; + VT.Margin := 2; + VT.DefaultText := '-'; // "Node" by default // Disable hottracking in non-Vista mode, looks ugly in XP, but nice in Vista if (toUseExplorerTheme in VT.TreeOptions.PaintOptions) and (Win32MajorVersion >= 6) then VT.TreeOptions.PaintOptions := VT.TreeOptions.PaintOptions + [toHotTrack] @@ -1427,7 +1552,7 @@ procedure FixVT(VT: TVirtualStringTree; MultiLineCount: Word=1); else VT.HintMode := hmTooltip; // Just a quick tooltip for clipped nodes // Apply case insensitive incremental search event - if VT.IncrementalSearch <> VirtualTrees.isNone then + if VT.IncrementalSearch <> VirtualTrees.Types.isNone then VT.OnIncrementalSearch := Mainform.AnyGridIncrementalSearch; VT.OnStartOperation := Mainform.AnyGridStartOperation; VT.OnEndOperation := Mainform.AnyGridEndOperation; @@ -1523,6 +1648,7 @@ function SelectNode(VT: TVirtualStringTree; idx: Int64; ParentNode: PVirtualNode function SelectNode(VT: TVirtualStringTree; Node: PVirtualNode; ClearSelection: Boolean=True): Boolean; overload; var OldFocus: PVirtualNode; + MinimumColumnIndex: TColumnIndex; begin if Node = VT.RootNode then Node := nil; @@ -1535,7 +1661,9 @@ function SelectNode(VT: TVirtualStringTree; Node: PVirtualNode; ClearSelection: if ClearSelection then VT.ClearSelection; VT.FocusedNode := Node; - VT.FocusedColumn := VT.Header.Columns.GetFirstVisibleColumn(True); + 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 @@ -1766,9 +1894,9 @@ function TSortItems.ComposeOrderClause(Connection: TDBConnection): String; if Result <> '' then Result := Result + ', '; if SortItem.Order = sioAscending then - SortOrder := Connection.GetSQLSpecifity(spOrderAsc) + SortOrder := Connection.SqlProvider.GetSql(qOrderAsc) else - SortOrder := Connection.GetSQLSpecifity(spOrderDesc); + SortOrder := Connection.SqlProvider.GetSql(qOrderDesc); Result := Result + Connection.QuoteIdent(SortItem.Column) + ' ' + SortOrder; end; end; @@ -2151,7 +2279,7 @@ function CompareAnyNode(Text1, Text2: String): Integer; end; if (not NumberMode) or (Result=0) then begin // Compare Strings - Result := CompareText(Text1, Text2); + Result := CompareText(Text1, Text2, loUserLocale); end; end; @@ -2310,6 +2438,13 @@ function IsNotEmpty(Str: String): Boolean; Result := Str <> ''; end; +function IfEmpty(Str: String; WhenEmpty: String): String; +begin + if Str.IsEmpty then + Result := WhenEmpty + else + Result := Str; +end; function MessageDialog(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons): Integer; begin @@ -2344,7 +2479,7 @@ function MessageDialog(const Title, Msg: string; DlgType: TMsgDlgType; Buttons: cap := _(BtnCaption); for i:=1 to Length(cap) do begin // Auto apply hotkey - if (Pos(LowerCase(cap[i]), Hotkeys) = 0) and TCharacter.IsLetter(cap[i]) then begin + if (Pos(LowerCase(cap[i]), Hotkeys) = 0) and cap[i].IsLetter then begin Hotkeys := Hotkeys + LowerCase(cap[i]); Insert('&', cap, i); break; @@ -2377,11 +2512,14 @@ function MessageDialog(const Title, Msg: string; DlgType: TMsgDlgType; Buttons: end; if Title <> Dialog.Caption then Dialog.Title := Title; - if Assigned(MainForm) and (MainForm.ActiveConnection <> nil) then + if Assigned(MainForm) and (MainForm.ActiveConnection <> nil) and (MainForm.ActiveConnection.Parameters <> nil) then Dialog.Caption := MainForm.ActiveConnection.Parameters.SessionName + ': ' + Dialog.Caption; rx := TRegExpr.Create; - rx.Expression := 'https?://\S+'; - Dialog.Text := rx.Replace(Msg, '$0', True); + rx.Expression := 'https?://[^\s"]+'; + if ThemeIsDark then + Dialog.Text := Msg + else // See issue #2036 + Dialog.Text := rx.Replace(Msg, '$0', True); rx.Free; // Main icon, and footer link @@ -2525,8 +2663,9 @@ procedure ParseCommandLine(CommandLine: String; var ConnectionParams: TConnectio var rx: TRegExpr; ExeName, SessName, Host, Lib, Port, User, Pass, Socket, AllDatabases, - SSLPrivateKey, SSLCACertificate, SSLCertificate, SSLCipher: String; - NetType, WindowsAuth, WantSSL, CleartextPluginEnabled: Integer; + SSLPrivateKey, SSLCACertificate, SSLCertificate, SSLCipher, + SshExe, SshHost, SshPort, SshLocalPort, SshUser, SshPassword, SshKey, SshTimeout: String; + NetType, WindowsAuth, WantSSL, CleartextPluginEnabled, SSLVerification: Integer; AbsentFiles: TStringList; function GetParamValue(ShortName, LongName: String): String; @@ -2605,7 +2744,16 @@ procedure ParseCommandLine(CommandLine: String; var ConnectionParams: TConnectio 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 + SshExe := GetParamValue('se', 'ssh-executable'); + SshHost := GetParamValue('sh', 'ssh-host'); + SshPort := GetParamValue('sP', 'ssh-port'); + SshLocalPort := GetParamValue('sLP', 'ssh-local-port'); + SshUser := GetParamValue('su', 'ssh-user'); + SshPassword := GetParamValue('sp', 'ssh-password'); + SshKey := GetParamValue('sk', 'ssh-key'); + SshTimeout := GetParamValue('st', 'ssh-timeout'); if (Host <> '') or (User <> '') or (Pass <> '') or (Port <> '') or (Socket <> '') or (AllDatabases <> '') then begin if not Assigned(ConnectionParams) then begin @@ -2641,13 +2789,39 @@ procedure ParseCommandLine(CommandLine: String; var ConnectionParams: TConnectio 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; + if not SshHost.IsEmpty then begin + ConnectionParams.SSHActive := True; + if not SshExe.IsEmpty then + ConnectionParams.SSHExe := SshExe; + if not SshHost.IsEmpty then + ConnectionParams.SSHHost := SshHost; + if not SshPort.IsEmpty then + ConnectionParams.SSHPort := StrToIntDef(SshPort, ConnectionParams.SSHPort); + if not SshLocalPort.IsEmpty then + ConnectionParams.SSHLocalPort := StrToIntDef(SshLocalPort, ConnectionParams.SSHLocalPort); + if not SshUser.IsEmpty then + ConnectionParams.SSHUser := SshUser; + if not SshPassword.IsEmpty then + ConnectionParams.SSHPassword := SshPassword; + if not SshKey.IsEmpty then + ConnectionParams.SSHPrivateKey := SshKey; + if not SshTimeout.IsEmpty then + ConnectionParams.SSHTimeout := StrToIntDef(SshTimeout, ConnectionParams.SSHTimeout); + end; + + if ConnectionParams.SessionPath.IsEmpty then begin + // Ensure we have a (random) session name to pass to InitConnection + ConnectionParams.SessionPath := IfEmpty(ConnectionParams.Hostname, 'temp')+'-'+GeneratePassword(4); + end; + + // Delete stored session in Destroy: + ConnectionParams.DeleteAfterUse := True; end; // Check for valid filename(s) in parameters. @@ -2767,6 +2941,34 @@ function GetExecutableBits: Byte; {$ENDIF} end; +procedure GetExecutableVersion(FileName: String; var MajorVer, MinorVer, ReleaseVer, RevisionVer: Word); +var + dwInfoSize, // Size of VERSIONINFO structure + dwVerSize, // Size of Version Info Data + dwWnd: DWORD; // Handle for the size call. + FI: PVSFixedFileInfo; // Delphi structure; see WINDOWS.PAS + ptrVerBuf: Pointer; +begin + // Detect version of given executable or library + MajorVer := 0; + MinorVer := 0; + ReleaseVer := 0; + RevisionVer := 0; + try + dwInfoSize := GetFileVersionInfoSize(PChar(FileName), dwWnd); + GetMem(ptrVerBuf, dwInfoSize); + GetFileVersionInfo(PChar(FileName), dwWnd, dwInfoSize, ptrVerBuf); + VerQueryValue(ptrVerBuf, '\', Pointer(FI), dwVerSize ); + MajorVer := HiWord(FI.dwFileVersionMS); + MinorVer := LoWord(FI.dwFileVersionMS); + ReleaseVer := HiWord(FI.dwFileVersionLS); + RevisionVer := LoWord(FI.dwFileVersionLS); + FreeMem(ptrVerBuf); + except + // Silence any error + end; +end; + procedure Help(Sender: TObject; Anchor: String); var @@ -2848,13 +3050,15 @@ function GetThemeColor(Color: TColor): TColor; end; -function ThemeIsDark(ThemeName: String): Boolean; +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; @@ -2895,7 +3099,7 @@ procedure ToggleCheckBoxWithoutClick(chk: TCheckBox; State: Boolean); 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{5}\color{%s}%s'; + 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; @@ -2987,6 +3191,23 @@ function UserAgent(OwnerComponent: TComponent): String; end; +function CodeIndent(Steps: Integer=1): String; +begin + // Provide tab or spaces for indentation, uniquely used for all SQL statements + if AppSettings.ReadBool(asTabsToSpaces) then + Result := StringOfChar(' ', AppSettings.ReadInt(asTabWidth) * Steps) + else + Result := StringOfChar(#9, Steps); +end; + + +function EscapeHotkeyPrefix(Text: String): String; +begin + // Issue #1992: Escape ampersand in caption of menus and tabs, preventing underlined hotkey generation + Result := StringReplace(Text, Vcl.Menus.cHotkeyPrefix, Vcl.Menus.cHotkeyPrefix + Vcl.Menus.cHotkeyPrefix, [rfReplaceAll]); +end; + + { Get SID of current Windows user, probably useful in the future function GetCurrentUserSID: string; type @@ -3084,9 +3305,9 @@ procedure TQueryThread.Execute; end else begin // Concat queries up to a size of max_allowed_packet if MaxAllowedPacket = 0 then begin - FConnection.LockedByThread := Self; + FConnection.SetLockedByThread(Self); MaxAllowedPacket := FConnection.MaxAllowedPacket; - FConnection.LockedByThread := nil; + FConnection.SetLockedByThread(nil); // TODO: Log('Detected maximum allowed packet size: '+FormatByteNumber(MaxAllowedPacket), lcDebug); end; BatchStartOffset := FBatch[i].LeftOffset; @@ -3109,7 +3330,7 @@ procedure TQueryThread.Execute; end; Synchronize(procedure begin MainForm.BeforeQueryExecution(Self); end); try - FConnection.LockedByThread := Self; + 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 @@ -3128,13 +3349,13 @@ procedure TQueryThread.Execute; Inc(FWarningCount, FConnection.WarningCount); except on E:EDbError do begin - if FStopOnErrors or (i = FBatch.Count - 1) then begin + if FStopOnErrors or (i = FBatch.Count) then begin FErrorMessage := E.Message; ErrorAborted := True; end; end; end; - FConnection.LockedByThread := nil; + FConnection.SetLockedByThread(nil); Synchronize(procedure begin MainForm.AfterQueryExecution(Self); end); // Check if FAborted is set by the main thread, to avoid proceeding the loop in case // FStopOnErrors is set to false @@ -3183,6 +3404,28 @@ function TSQLSentence.GetSQLWithoutComments: String; { TSQLBatch } +constructor TSQLBatch.Create(NetTypeGroup: TNetTypeGroup); +begin + inherited Create; + FQuotes := THashedStringList.Create; + FQuotes.CaseSensitive := True; + FQuotes.Sorted := True; + FQuotes.Add('"'); + FQuotes.Add(''''); + FEscape := '\'; + case NetTypeGroup of + ngMySQL: FQuotes.Add('`'); // MySQL/MariaDB only + ngPgSQL: FQuotes.Add('$$'); // PostgreSQL only ($abc$ unsupported) + ngSQLite: FEscape := ''''; + end; +end; + +destructor TSQLBatch.Destroy; +begin + FQuotes.Free; + inherited; +end; + function TSQLBatch.GetSize: Integer; var Query: TSQLSentence; @@ -3197,13 +3440,12 @@ function TSQLBatch.GetSize: Integer; procedure TSQLBatch.SetSQL(Value: String); var i, AllLen, DelimLen, DelimStart, LastLeftOffset, RightOffset: Integer; - c, n, LastStringEncloser: Char; - Delim, DelimTest, QueryTest: String; + c, n: Char; + Delim, DelimTest, QueryTest, LastQuote, cn: String; InString, InComment, InBigComment, InEscape: Boolean; Marker: TSQLSentence; rx: TRegExpr; const - StringEnclosers = ['"', '''', '`']; NewLines = [#13, #10]; WhiteSpaces = NewLines + [#9, ' ']; begin @@ -3214,11 +3456,12 @@ procedure TSQLBatch.SetSQL(Value: String); 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; + + InString := False; // c is in "enclosed string" or `identifier` + InComment := False; // c is in one-line comment (# or --) + InBigComment := False; // c is in /* multi-line */ or /*! condictional comment */ + InEscape := False; // Previous char was backslash + LastQuote := #0; DelimLen := Length(Delim); rx := TRegExpr.Create; rx.Expression := '^\s*DELIMITER\s+(\S+)'; @@ -3229,20 +3472,27 @@ procedure TSQLBatch.SetSQL(Value: String); Inc(i); // Current and next char c := FSQL[i]; - if i < AllLen then n := FSQL[i+1] - else n := #0; + if i < AllLen then + n := FSQL[i+1] + else + n := #0; + cn := c + n; - // Check for comment syntax and for enclosed literals, so a query delimiter can be ignored + // Check for comment syntax, 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; + // Check for enclosed literals, so a query delimiter can be ignored + if (not InEscape) and (not InComment) and (not InBigComment) and (FQuotes.Contains(c) or FQuotes.Contains(cn)) then begin + if not InString then begin + InString := True; + LastQuote := IfThen(FQuotes.Contains(c), c, cn); + end + else if (c = LastQuote) or (cn = LastQuote) then begin + InString := False; end; end; if (CharInSet(c, NewLines) and (not CharInSet(n, NewLines))) or (i = 1) then begin @@ -3257,7 +3507,7 @@ procedure TSQLBatch.SetSQL(Value: String); end; end; if not InEscape then - InEscape := c = '\' + InEscape := c = FEscape else InEscape := False; @@ -3285,6 +3535,7 @@ procedure TSQLBatch.SetSQL(Value: String); end; end; end; + end; function TSQLBatch.GetSQLWithoutComments: String; @@ -3304,13 +3555,11 @@ class function TSQLBatch.GetSQLWithoutComments(FullSQL: String): String; 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 i > 1 then Prev1 := FullSQL[i-1] else Prev1 := #0; + if i > 2 then Prev2 := FullSQL[i-2] else Prev2 := #0; if (Cur = '*') and (Prev1 = '/') then begin InMultiLineComment := True; @@ -3329,7 +3578,7 @@ class function TSQLBatch.GetSQLWithoutComments(FullSQL: String): String; else if Cur = '#' then begin InLineComment := True; end - else if (Cur = ' ') and (Prev1 = '-') and (Prev2 = '-') then begin + else if (not InLineComment) and (Cur = ' ') and (Prev1 = '-') and (Prev2 = '-') then begin InLineComment := True; System.Delete(Result, Length(Result)-1, 2); // Delete comment chars end; @@ -3346,7 +3595,6 @@ class function TSQLBatch.GetSQLWithoutComments(FullSQL: String): String; constructor THttpDownload.Create(Owner: TComponent); begin FBytesRead := -1; - FContentLength := -1; FOwner := Owner; FTimeOut := 10; end; @@ -3375,24 +3623,10 @@ procedure THttpDownload.SendRequest(Filename: String); 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; @@ -3531,11 +3765,11 @@ procedure TClipboardHelper.SetTryAsText(AValue: String); procedure TWinControlHelper.TrySetFocus; begin try - if Enabled and CanFocus then + if Enabled + and CanFocus then SetFocus; except - on E:EInvalidOperation do - MessageBeep(MB_ICONWARNING); + MessageBeep(MB_ICONWARNING); end; end; @@ -3546,7 +3780,6 @@ constructor TAppSettings.Create; var rx: TRegExpr; i: Integer; - DefaultSnippetsDirectory: String; PortableLockFile: String; NewFileHandle: THandle; begin @@ -3555,7 +3788,7 @@ constructor TAppSettings.Create; FReads := 0; FWrites := 0; - PortableLockFile := ExtractFilePath(ParamStr(0)) + FPortableLockFileBase; + PortableLockFile := GetAppDir + FPortableLockFileBase; // Use filename from command line. If not given, use file in directory of executable. rx := TRegExpr.Create; @@ -3568,7 +3801,7 @@ constructor TAppSettings.Create; end; // Default settings file, if not given per command line if FSettingsFile = '' then - FSettingsFile := ExtractFilePath(ParamStr(0)) + 'portable_settings.txt'; + FSettingsFile := GetAppDir + '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); @@ -3678,6 +3911,7 @@ constructor TAppSettings.Create; InitSetting(asUser, 'User', 0, False, '', True); InitSetting(asPassword, 'Password', 0, False, '', True); InitSetting(asCleartextPluginEnabled, 'CleartextPluginEnabled', 0, False, '', True); + InitSetting(asForceUnicode, 'ForceUnicode', 0, True, '', True); InitSetting(asWindowsAuth, 'WindowsAuth', 0, False, '', True); InitSetting(asLoginPrompt, 'LoginPrompt', 0, False, '', True); InitSetting(asPort, 'Port', 0, False, '', True); @@ -3698,6 +3932,7 @@ constructor TAppSettings.Create; 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); @@ -3721,6 +3956,7 @@ constructor TAppSettings.Create; InitSetting(asExportSQLServerDatabase, 'ExportSQL_ServerDatabase', 0, False, ''); InitSetting(asExportSQLOutput, 'ExportSQL_Output', 0); InitSetting(asExportSQLAddComments, 'ExportSQLAddComments', 0, True); + InitSetting(asExportSQLTransactions, 'ExportSQLTransactions', 0, False); InitSetting(asExportSQLRemoveAutoIncrement, 'ExportSQLRemoveAutoIncrement', 0, False); InitSetting(asExportSQLRemoveDefiner, 'ExportSQLRemoveDefiner', 0, True); InitSetting(asGridExportWindowWidth, 'GridExportWindowWidth', 400); @@ -3734,8 +3970,10 @@ constructor TAppSettings.Create; InitSetting(asGridExportSelection, 'GridExportSelection', 1); InitSetting(asGridExportColumnNames, 'GridExportColumnNames', 0, True); InitSetting(asGridExportIncludeAutoInc, 'GridExportAutoInc', 0, True); + InitSetting(asGridExportFocusedColumnOnly, 'GridExportFocusedColumnOnly', 0, False); InitSetting(asGridExportIncludeQuery, 'GridExportIncludeQuery', 0, False); InitSetting(asGridExportRemoveLinebreaks, 'GridExportRemoveLinebreaks', 0, False); + InitSetting(asGridExportOpenFile, 'GridExportOpenFile', 0, False); InitSetting(asGridExportSeparator, 'GridExportSeparator', 0, False, ';'); InitSetting(asGridExportEncloser, 'GridExportEncloser', 0, False, ''); InitSetting(asGridExportTerminator, 'GridExportTerminator', 0, False, '\r\n'); @@ -3754,7 +3992,7 @@ constructor TAppSettings.Create; InitSetting(asCSVImportTerminator, 'CSVTerminator', 0, False, '\r\n'); InitSetting(asCSVImportFieldEscaper, 'CSVImportFieldEscaperV2', 0, False, '"'); InitSetting(asCSVImportWindowWidth, 'CSVImportWindowWidth', 530); - InitSetting(asCSVImportWindowHeight, 'CSVImportWindowHeight', 530); + InitSetting(asCSVImportWindowHeight, 'CSVImportWindowHeight', 550); InitSetting(asCSVImportFilename, 'loadfilename', 0, False, ''); InitSetting(asCSVImportFieldsEnclosedOptionally, 'CSVImportFieldsEnclosedOptionallyV2', 0, True); InitSetting(asCSVImportIgnoreLines, 'CSVImportIgnoreLines', 1); @@ -3762,6 +4000,7 @@ constructor TAppSettings.Create; 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); @@ -3787,6 +4026,7 @@ constructor TAppSettings.Create; InitSetting(asSelectDBOWindowWidth, 'SelectDBO_WindowWidth', 250); InitSetting(asSelectDBOWindowHeight, 'SelectDBO_WindowHeight', 350); InitSetting(asSessionManagerListWidth, 'SessionManager_ListWidth', 220); + InitSetting(asSessionManagerListFoldersAtTop, 'SessionManager_ListFoldersAtTop', 0, True); InitSetting(asSessionManagerWindowWidth, 'SessionManager_WindowWidth', 700); InitSetting(asSessionManagerWindowHeight, 'SessionManager_WindowHeight', 490); InitSetting(asSessionManagerWindowLeft, 'SessionManager_WindowLeft', 50); @@ -3814,23 +4054,20 @@ constructor TAppSettings.Create; InitSetting(asSequalSuggestPrompt, 'SequalSuggestPrompt', 0, False, ''); InitSetting(asSequalSuggestRecentPrompts, 'SequalSuggestRecentPrompts', 0, False, ''); InitSetting(asReformatter, 'Reformatter', 0); + InitSetting(asReformatterNoDialog, 'ReformatterNoDialog', 0); InitSetting(asAlwaysGenerateFilter, 'AlwaysGenerateFilter', 0, False); - - // Default folder for snippets - if FPortableMode then - DefaultSnippetsDirectory := ExtractFilePath(ParamStr(0)) - else - DefaultSnippetsDirectory := DirnameUserDocuments; - DefaultSnippetsDirectory := DefaultSnippetsDirectory + 'Snippets\'; - InitSetting(asCustomSnippetsDirectory, 'CustomSnippetsDirectory', 0, False, DefaultSnippetsDirectory); + InitSetting(asDisplayReverseForeignKeys, 'DisplayReverseForeignKeys', 0, False); + InitSetting(asGenerateDataNumRows, 'GenerateDataNumRows', 1000); + InitSetting(asGenerateDataNullAmount, 'GenerateDataNullAmount', 10); + InitSetting(asCustomSnippetsDirectory, 'CustomSnippetsDirectory', 0, False, DirnameUserDocuments + 'Snippets\'); InitSetting(asPromptSaveFileOnTabClose, 'PromptSaveFileOnTabClose', 0, True); // Restore tabs feature crashes often on old XP systems, see https://www.heidisql.com/forum.php?t=34044 InitSetting(asRestoreTabs, 'RestoreTabs', 0, Win32MajorVersion >= 6); InitSetting(asTabCloseOnDoubleClick, 'TabCloseOnDoubleClick', 0, True); InitSetting(asTabCloseOnMiddleClick, 'TabCloseOnMiddleClick', 0, True); InitSetting(asTabsInMultipleLines, 'TabsInMultipleLines', 0, True); + InitSetting(asTabIconsGrayscaleMode, 'TabIconsGrayscaleMode', 1); InitSetting(asWarnUnsafeUpdates, 'WarnUnsafeUpdates', 0, True); - InitSetting(asQueryWarningsMessage, 'QueryWarningsMessage', 0, True); InitSetting(asQueryGridLongSortRowNum, 'QueryGridLongSortRowNum', 10000); InitSetting(asCompletionProposal, 'CompletionProposal', 0, True); InitSetting(asCompletionProposalInterval, 'CompletionProposalInterval', 500); @@ -3873,6 +4110,8 @@ constructor TAppSettings.Create; InitSetting(asRowBackgroundOdd, 'RowBackgroundOdd', clNone); InitSetting(asGroupTreeObjects, 'GroupTreeObjects', 0, False); InitSetting(asDisplayObjectSizeColumn, 'DisplayObjectSizeColumn', 0, True); + InitSetting(asDisplayLogPanel, 'DisplayLogPanel', 0, True); + InitSetting(asDisplayTreeFilters, 'DisplayTreeFilters', 0, True); InitSetting(asActionShortcut1, 'Shortcut1_%s', 0); InitSetting(asActionShortcut2, 'Shortcut2_%s', 0); InitSetting(asHighlighterForeground, 'SQL Attr %s Foreground', 0); @@ -3898,7 +4137,7 @@ constructor TAppSettings.Create; InitSetting(asColumnSelectorHeight, 'ColumnSelectorHeight', 270, False, ''); InitSetting(asDonatedEmail, 'DonatedEmail', 0, False, ''); InitSetting(asFavoriteObjects, 'FavoriteObjects', 0, False, '', True); - InitSetting(asFavoriteObjectsOnly, 'FavoriteObjectsOnly', 0, False); + InitSetting(asFavoriteObjectsOnly, 'FavoriteObjectsOnly', 0, False); // No longer used InitSetting(asFullTableStatus, 'FullTableStatus', 0, True, '', True); InitSetting(asLineBreakStyle, 'LineBreakStyle', Integer(lbsWindows)); InitSetting(asPreferencesWindowWidth, 'PreferencesWindowWidth', 740); @@ -3910,6 +4149,7 @@ constructor TAppSettings.Create; 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); @@ -4451,7 +4691,7 @@ function TAppSettings.ExportSettings(Filename: String): Boolean; // Save registry settings to file Content := ''; ReadKeyToContent(FBasePath); - SaveUnicodeFile(FileName, Content); + SaveUnicodeFile(FileName, Content, UTF8NoBOMEncoding); Result := True; end; @@ -4466,7 +4706,7 @@ function TAppSettings.ExportSettings: Boolean; except on E:Exception do begin FPortableModeReadOnly := True; - Raise Exception.Create(E.Message + CRLF + CRLF + 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; @@ -4477,19 +4717,29 @@ function TAppSettings.ExportSettings: Boolean; function TAppSettings.DirnameUserAppData: String; begin - // User folder for HeidiSQL's data (\Application Data) - Result := GetShellFolder(FOLDERID_RoamingAppData) + '\' + APPNAME + '\'; - if not DirectoryExists(Result) then begin - ForceDirectories(Result); + // C:\Users\mike\AppData\Roaming\HeidiSQL\ + if PortableMode then begin + Result := GetAppDir; + end + else begin + Result := GetShellFolder(FOLDERID_RoamingAppData) + '\' + APPNAME + '\'; + if not DirectoryExists(Result) then begin + ForceDirectories(Result); + end; end; end; function TAppSettings.DirnameUserDocuments: String; begin - // "HeidiSQL" folder under user's documents folder, e.g. c:\Users\Mike\Documents\HeidiSQL\ - Result := GetShellFolder(FOLDERID_Documents) + '\' + APPNAME + '\'; - // Do not auto-create it, as we only use it for snippets which can also have a custom path. + // C:\Users\mike\Documents\HeidiSQL\ + if PortableMode then begin + Result := GetAppDir; + end + else begin + Result := GetShellFolder(FOLDERID_Documents) + '\' + APPNAME + '\'; + // Do not auto-create it, as we only use it for snippets which can also have a custom path. + end; end; @@ -4509,11 +4759,7 @@ function TAppSettings.DirnameSnippets: String; function TAppSettings.DirnameBackups: String; begin // Create backup folder if it does not exist and return it - if PortableMode then begin - Result := ExtractFilePath(Application.ExeName) + 'Backups\' - end else begin - Result := DirnameUserAppData + 'Backups\'; - end; + Result := DirnameUserAppData + 'Backups\'; if not DirectoryExists(Result) then begin ForceDirectories(Result); end; @@ -4522,11 +4768,7 @@ function TAppSettings.DirnameBackups: String; function TAppSettings.DirnameHighlighters: string; begin - if PortableMode then begin - Result := ExtractFilePath(Application.ExeName) + 'Highlighters\' - end else begin - Result := DirnameUserAppData + 'Highlighters\'; - end; + Result := DirnameUserAppData + 'Highlighters\'; if not DirectoryExists(Result) then begin ForceDirectories(Result); end; diff --git a/source/column_selection.pas b/source/column_selection.pas index 087e7bb4e..4dfaddbe4 100644 --- a/source/column_selection.pas +++ b/source/column_selection.pas @@ -89,6 +89,7 @@ procedure TfrmColumnSelection.btnOKClick(Sender: TObject); i: Integer; Col: String; begin + AppSettings.WriteBool(asDisplayedColumnsSorted, chkSort.Checked); AppSettings.WriteBool(asShowRowId, chkShowRowId.Checked); // Prepare string for storing in registry. // Use quote-character as separator to ensure columnnames can diff --git a/source/connections.dfm b/source/connections.dfm index a8daaad50..4199c289d 100644 --- a/source/connections.dfm +++ b/source/connections.dfm @@ -51,7 +51,7 @@ object connform: Tconnform OnClick = btnSaveClick end object btnOpen: TButton - Left = 489 + Left = 485 Top = 473 Width = 80 Height = 25 @@ -63,7 +63,7 @@ object connform: Tconnform OnClick = btnOpenClick end object btnCancel: TButton - Left = 575 + Left = 571 Top = 473 Width = 80 Height = 25 @@ -138,7 +138,7 @@ object connform: Tconnform object btnImportSettings: TButton Left = 10 Top = 184 - Width = 497 + Width = 493 Height = 25 Anchors = [akLeft, akTop, akRight] Caption = 'Import settings ...' @@ -157,67 +157,67 @@ object connform: Tconnform object lblPort: TLabel Left = 3 Top = 199 - Width = 24 - Height = 13 + Width = 27 + Height = 14 Caption = 'Port:' FocusControl = editPort end object lblPassword: TLabel Left = 3 Top = 174 - Width = 50 - Height = 13 + Width = 55 + Height = 14 Caption = 'Password:' FocusControl = editPassword end object lblHost: TLabel Left = 3 Top = 76 - Width = 72 - Height = 13 + Width = 83 + Height = 14 Caption = 'Hostname / IP:' FocusControl = editHost end object lblUsername: TLabel Left = 3 Top = 149 - Width = 26 - Height = 13 + Width = 28 + Height = 14 Caption = 'User:' FocusControl = editUsername end object lblNetworkType: TLabel Left = 3 Top = 12 - Width = 69 - Height = 13 + Width = 80 + Height = 14 Caption = 'Network type:' end object lblDatabase: TLabel Left = 3 Top = 247 - Width = 55 - Height = 13 + Width = 59 + Height = 14 Caption = 'Databases:' end object lblComment: TLabel Left = 3 Top = 274 - Width = 49 - Height = 13 + Width = 57 + Height = 14 Caption = 'Comment:' end object lblLibrary: TLabel Left = 3 Top = 39 - Width = 37 - Height = 13 + Width = 39 + Height = 14 Caption = 'Library:' end object chkCompressed: TCheckBox Left = 190 Top = 221 - Width = 320 + Width = 316 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Compressed client/server protocol' @@ -228,7 +228,7 @@ object connform: Tconnform Left = 190 Top = 196 Width = 57 - Height = 21 + Height = 22 TabOrder = 7 Text = '0' OnChange = Modification @@ -237,7 +237,7 @@ object connform: Tconnform Left = 247 Top = 196 Width = 16 - Height = 21 + Height = 22 Associate = editPort Max = 2147483647 TabOrder = 8 @@ -247,28 +247,31 @@ object connform: Tconnform object editPassword: TEdit Left = 190 Top = 171 - Width = 320 - Height = 21 + Width = 316 + Height = 22 Anchors = [akLeft, akTop, akRight] PasswordChar = '*' TabOrder = 6 OnChange = Modification end - object editUsername: TEdit + object editUsername: TButtonedEdit Left = 190 Top = 146 - Width = 320 - Height = 21 + Width = 316 + Height = 22 Anchors = [akLeft, akTop, akRight] + Images = MainForm.VirtualImageListMain + RightButton.ImageIndex = 75 TabOrder = 5 OnChange = Modification OnExit = editTrim + OnRightButtonClick = editUsernameRightButtonClick end object editHost: TButtonedEdit Left = 190 Top = 73 - Width = 320 - Height = 21 + Width = 316 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.DropDownMenu = popupHost @@ -282,8 +285,8 @@ object connform: Tconnform object comboNetType: TComboBoxEx Left = 190 Top = 8 - Width = 320 - Height = 22 + Width = 316 + Height = 23 ItemsEx = <> Style = csExDropDownList Anchors = [akLeft, akTop, akRight] @@ -295,7 +298,7 @@ object connform: Tconnform object chkLoginPrompt: TCheckBox Left = 190 Top = 100 - Width = 320 + Width = 316 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Prompt for credentials' @@ -305,7 +308,7 @@ object connform: Tconnform object chkWindowsAuth: TCheckBox Left = 190 Top = 123 - Width = 320 + Width = 316 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Use Windows authentication' @@ -316,7 +319,7 @@ object connform: Tconnform object memoComment: TMemo Left = 190 Top = 271 - Width = 320 + Width = 316 Height = 153 Anchors = [akLeft, akTop, akRight, akBottom] ScrollBars = ssVertical @@ -326,8 +329,8 @@ object connform: Tconnform object editDatabases: TButtonedEdit Left = 190 Top = 244 - Width = 320 - Height = 21 + Width = 316 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.ImageIndex = 75 @@ -340,8 +343,8 @@ object connform: Tconnform object comboLibrary: TComboBox Left = 190 Top = 36 - Width = 320 - Height = 21 + Width = 316 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] TabOrder = 1 @@ -358,62 +361,62 @@ object connform: Tconnform object lblSSHLocalPort: TLabel Left = 3 Top = 202 - Width = 51 - Height = 13 + Width = 58 + Height = 14 Caption = 'Local port:' FocusControl = editSSHlocalport end object lblSSHUser: TLabel Left = 3 Top = 94 - Width = 52 - Height = 13 + Width = 58 + Height = 14 Caption = 'Username:' FocusControl = editSSHUser end object lblSSHPassword: TLabel Left = 3 Top = 121 - Width = 50 - Height = 13 + Width = 55 + Height = 14 Caption = 'Password:' FocusControl = editSSHPassword end object lblSSHExe: TLabel Left = 3 Top = 40 - Width = 87 - Height = 13 + Width = 90 + Height = 14 Caption = 'SSH executable:' end object lblSSHhost: TLabel Left = 3 Top = 67 - Width = 81 - Height = 13 + Width = 93 + Height = 14 Caption = 'SSH host + port:' FocusControl = editSSHhost end object lblSSHkeyfile: TLabel Left = 3 Top = 175 - Width = 75 - Height = 13 + Width = 83 + Height = 14 Caption = 'Private key file:' FocusControl = editSSHPrivateKey end object lblSSHTimeout: TLabel Left = 3 Top = 148 - Width = 86 - Height = 13 + Width = 73 + Height = 14 Caption = 'SSH timeout:' end object editSSHlocalport: TEdit Left = 190 Top = 199 - Width = 320 - Height = 21 + Width = 316 + Height = 22 Anchors = [akLeft, akTop, akRight] NumbersOnly = True TabOrder = 9 @@ -423,8 +426,8 @@ object connform: Tconnform object editSSHUser: TEdit Left = 190 Top = 91 - Width = 320 - Height = 21 + Width = 316 + Height = 22 Anchors = [akLeft, akTop, akRight] TabOrder = 4 Text = 'editSSHUser' @@ -435,8 +438,8 @@ object connform: Tconnform object editSSHPassword: TEdit Left = 190 Top = 118 - Width = 320 - Height = 21 + Width = 316 + Height = 22 Anchors = [akLeft, akTop, akRight] PasswordChar = '*' TabOrder = 5 @@ -447,8 +450,8 @@ object connform: Tconnform object editSSHhost: TEdit Left = 190 Top = 64 - Width = 260 - Height = 21 + Width = 256 + Height = 22 Anchors = [akLeft, akTop, akRight] TabOrder = 2 Text = 'editSSHhost' @@ -456,10 +459,10 @@ object connform: Tconnform OnExit = editTrim end object editSSHport: TEdit - Left = 456 + Left = 452 Top = 64 Width = 54 - Height = 21 + Height = 22 Anchors = [akTop, akRight] NumbersOnly = True TabOrder = 3 @@ -469,8 +472,8 @@ object connform: Tconnform object editSSHPrivateKey: TButtonedEdit Left = 190 Top = 172 - Width = 320 - Height = 21 + Width = 316 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.ImageIndex = 51 @@ -487,7 +490,7 @@ object connform: Tconnform Left = 190 Top = 145 Width = 60 - Height = 21 + Height = 22 TabOrder = 6 Text = '1' OnChange = Modification @@ -496,7 +499,7 @@ object connform: Tconnform Left = 250 Top = 145 Width = 17 - Height = 21 + Height = 22 Associate = editSSHTimeout Min = 1 Position = 1 @@ -506,7 +509,7 @@ object connform: Tconnform object comboSSHExe: TComboBox Left = 190 Top = 36 - Width = 320 + Width = 316 Height = 22 Anchors = [akLeft, akTop, akRight] TabOrder = 1 @@ -516,7 +519,7 @@ object connform: Tconnform object chkSSHActive: TCheckBox Left = 190 Top = 13 - Width = 324 + Width = 320 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Use SSH tunnel' @@ -534,50 +537,50 @@ object connform: Tconnform object lblStartupScript: TLabel Left = 3 Top = 12 - Width = 69 - Height = 13 + Width = 78 + Height = 14 Caption = 'Startup script:' FocusControl = editStartupScript end object lblQueryTimeout: TLabel Left = 3 Top = 39 - Width = 73 - Height = 13 + Width = 84 + Height = 14 Caption = 'Query timeout:' end object lblKeepAlive: TLabel Left = 3 Top = 66 - Width = 106 - Height = 13 + Width = 120 + Height = 14 Caption = 'Ping every X seconds:' end object lblBackgroundColor: TLabel Left = 3 - Top = 162 - Width = 86 - Height = 13 + Top = 185 + Width = 98 + Height = 14 Caption = 'Background color:' end object lblIgnoreDatabasePattern: TLabel Left = 3 - Top = 190 - Width = 112 - Height = 13 + Top = 213 + Width = 126 + Height = 14 Caption = 'Hide database pattern:' end object lblLogFile: TLabel Left = 3 - Top = 229 - Width = 89 - Height = 13 + Top = 252 + Width = 102 + Height = 14 Caption = 'Log queries to file:' end object chkLocalTimeZone: TCheckBox Left = 190 Top = 90 - Width = 324 + Width = 320 Height = 17 Hint = 'Use your client time zone in date/time SQL functions, e.g. NOW()' + @@ -590,7 +593,7 @@ object connform: Tconnform object chkCleartextPluginEnabled: TCheckBox Left = 190 Top = 136 - Width = 324 + Width = 320 Height = 17 Hint = 'Send your password to the server in cleartext, for MySQL 5.5.47+' Anchors = [akLeft, akTop, akRight] @@ -601,8 +604,8 @@ object connform: Tconnform object editStartupScript: TButtonedEdit Left = 190 Top = 9 - Width = 324 - Height = 21 + Width = 320 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.ImageIndex = 51 @@ -616,7 +619,7 @@ object connform: Tconnform object chkFullTableStatus: TCheckBox Left = 190 Top = 113 - Width = 324 + Width = 320 Height = 17 Hint = 'Disable to speed up internal queries on databases with many tabl' + @@ -630,7 +633,7 @@ object connform: Tconnform Left = 190 Top = 36 Width = 90 - Height = 21 + Height = 22 NumbersOnly = True TabOrder = 1 Text = '0' @@ -640,7 +643,7 @@ object connform: Tconnform Left = 280 Top = 36 Width = 16 - Height = 21 + Height = 22 Associate = editQueryTimeout Max = 2147483646 TabOrder = 2 @@ -650,7 +653,7 @@ object connform: Tconnform Left = 190 Top = 63 Width = 90 - Height = 21 + Height = 22 TabOrder = 3 Text = '0' OnChange = Modification @@ -659,15 +662,15 @@ object connform: Tconnform Left = 280 Top = 63 Width = 16 - Height = 21 + Height = 22 Associate = editKeepAlive Max = 86400 TabOrder = 4 end object ColorBoxBackgroundColor: TColorBox Left = 190 - Top = 159 - Width = 324 + Top = 182 + Width = 320 Height = 22 NoneColorColor = clNone Selected = clNone @@ -680,9 +683,9 @@ object connform: Tconnform end object editIgnoreDatabasePattern: TEdit Left = 190 - Top = 187 - Width = 324 - Height = 21 + Top = 210 + Width = 320 + Height = 22 Anchors = [akLeft, akTop, akRight] TabOrder = 9 TextHint = 'Regular expression' @@ -690,8 +693,8 @@ object connform: Tconnform end object chkLogFileDdl: TCheckBox Left = 190 - Top = 253 - Width = 324 + Top = 276 + Width = 320 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'DDL queries (CREATE, ALTER, ...)' @@ -700,9 +703,9 @@ object connform: Tconnform end object editLogFilePath: TButtonedEdit Left = 190 - Top = 226 - Width = 324 - Height = 21 + Top = 249 + Width = 320 + Height = 22 Anchors = [akLeft, akTop, akRight] Enabled = False Images = MainForm.VirtualImageListMain @@ -714,14 +717,24 @@ object connform: Tconnform end object chkLogFileDml: TCheckBox Left = 190 - Top = 276 - Width = 324 + Top = 299 + Width = 320 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'DML queries (INSERT, UPDATE, ...)' TabOrder = 12 OnClick = Modification end + object chkForceUnicode: TCheckBox + Left = 190 + Top = 159 + Width = 320 + Height = 17 + Anchors = [akLeft, akTop, akRight] + Caption = 'Force Unicode (disable on old servers only)' + TabOrder = 13 + OnClick = Modification + end end object tabSSL: TTabSheet Caption = 'SSL' @@ -732,38 +745,45 @@ object connform: Tconnform object lblSSLPrivateKey: TLabel Left = 3 Top = 39 - Width = 78 - Height = 13 + Width = 88 + Height = 14 Caption = 'SSL private key:' FocusControl = editSSLPrivateKey end object lblSSLCACertificate: TLabel Left = 3 Top = 66 - Width = 89 - Height = 13 + Width = 101 + Height = 14 Caption = 'SSL CA certificate:' FocusControl = editSSLCACertificate end object lblSSLCertificate: TLabel Left = 3 Top = 93 - Width = 72 - Height = 13 + Width = 82 + Height = 14 Caption = 'SSL certificate:' FocusControl = editSSLCertificate end object lblSSLcipher: TLabel Left = 3 Top = 120 - Width = 53 - Height = 13 + Width = 61 + Height = 14 Caption = 'SSL cipher:' end + object lblSSLVerification: TLabel + Left = 3 + Top = 148 + Width = 121 + Height = 14 + Caption = 'Certificate verification:' + end object chkWantSSL: TCheckBox Left = 190 Top = 13 - Width = 324 + Width = 320 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Use SSL' @@ -774,9 +794,9 @@ object connform: Tconnform Left = 190 Top = 117 Width = 324 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] - TabOrder = 1 + TabOrder = 4 TextHint = 'List of permissible ciphers to use for SSL encryption' OnChange = Modification OnExit = editTrim @@ -785,12 +805,12 @@ object connform: Tconnform Left = 190 Top = 90 Width = 324 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.ImageIndex = 51 RightButton.Visible = True - TabOrder = 2 + TabOrder = 3 TextHint = 'Path to certificate file' OnChange = Modification OnDblClick = PickFile @@ -801,12 +821,12 @@ object connform: Tconnform Left = 190 Top = 63 Width = 324 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.ImageIndex = 51 RightButton.Visible = True - TabOrder = 3 + TabOrder = 2 TextHint = 'Path to certificate authority file' OnChange = Modification OnDblClick = PickFile @@ -817,18 +837,34 @@ object connform: Tconnform Left = 190 Top = 36 Width = 324 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.ImageIndex = 51 RightButton.Visible = True - TabOrder = 4 + TabOrder = 1 TextHint = 'Path to key file' OnChange = Modification OnDblClick = PickFile OnExit = editTrim OnRightButtonClick = PickFile end + object comboSSLVerification: TComboBox + Left = 190 + Top = 145 + Width = 324 + Height = 22 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + TabOrder = 5 + OnChange = Modification + Items.Strings = ( + 'No verification (insecure)' + 'Verify CA (insecure)' + + 'Verify CA and host name identity (may fail with self-signed cert' + + 's and wildcard cn)') + end end object tabStatistics: TTabSheet Caption = 'Statistics' @@ -837,63 +873,63 @@ object connform: Tconnform object lblLastConnectLeft: TLabel Left = 3 Top = 31 - Width = 65 - Height = 13 + Width = 75 + Height = 14 Caption = 'Last connect:' end object lblCounterLeft: TLabel Left = 3 Top = 50 - Width = 100 - Height = 13 + Width = 114 + Height = 14 Caption = 'Successful connects:' end object lblCreatedLeft: TLabel Left = 3 Top = 12 - Width = 43 - Height = 13 + Width = 47 + Height = 14 Caption = 'Created:' end object lblCreatedRight: TLabel Left = 190 Top = 12 - Width = 5 - Height = 13 + Width = 6 + Height = 14 Caption = '?' end object lblCounterRight1: TLabel Left = 190 Top = 50 - Width = 5 - Height = 13 + Width = 6 + Height = 14 Caption = '?' end object lblLastConnectRight: TLabel Left = 190 Top = 31 - Width = 5 - Height = 13 + Width = 6 + Height = 14 Caption = '?' end object lblCounterRight2: TLabel Left = 190 Top = 69 - Width = 5 - Height = 13 + Width = 6 + Height = 14 Caption = '?' end object lblCounterLeft2: TLabel Left = 3 Top = 69 - Width = 112 - Height = 13 + Width = 127 + Height = 14 Caption = 'Unsuccessful connects:' end end end object btnMore: TButton - Left = 661 + Left = 657 Top = 473 Width = 80 Height = 25 @@ -920,12 +956,13 @@ object connform: Tconnform TabOrder = 7 object ListSessions: TVirtualStringTree Left = 0 - Top = 26 + Top = 27 Width = 200 - Height = 432 + Height = 431 Align = alClient DragMode = dmAutomatic Header.AutoSizeIndex = -1 + Header.Height = 18 Header.Options = [hoAutoResize, hoColumnResize, hoDblClickResize, hoDrag, hoHotTrack, hoShowSortGlyphs, hoVisible, hoDisableAnimatedResize, hoAutoResizeInclCaption] Header.PopupMenu = MainForm.popupListHeader Header.SortColumn = 0 @@ -939,6 +976,7 @@ object connform: Tconnform TreeOptions.PaintOptions = [toHotTrack, toShowButtons, toShowDropmark, toShowRoot, toShowTreeLines, toThemeAware, toUseBlendedImages, toUseExplorerTheme, toHideTreeLinesIfThemed] TreeOptions.SelectionOptions = [toFullRowSelect, toRightClickSelect] OnBeforeCellPaint = ListSessionsBeforeCellPaint + OnCompareNodes = ListSessionsCompareNodes OnCreateEditor = ListSessionsCreateEditor OnDragOver = ListSessionsDragOver OnDragDrop = ListSessionsDragDrop @@ -964,29 +1002,35 @@ object connform: Tconnform Text = 'Host' end item - Options = [coAllowClick, coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coAllowFocus] + Options = [coAllowClick, coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 2 - Text = 'User' + Text = 'Port' + Width = 10 end item Options = [coAllowClick, coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coAllowFocus] Position = 3 + Text = 'User' + end + item + Options = [coAllowClick, coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coAllowFocus] + Position = 4 Text = 'Version' end item Options = [coAllowClick, coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] - Position = 4 + Position = 5 Text = 'Last connect' end item Alignment = taRightJustify Options = [coAllowClick, coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coAllowFocus] - Position = 5 + Position = 6 Text = 'Counter' end item Options = [coAllowClick, coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] - Position = 6 + Position = 7 Text = 'Comment' Width = 10 end> @@ -996,7 +1040,7 @@ object connform: Tconnform Left = 0 Top = 0 Width = 200 - Height = 21 + Height = 22 Margins.Left = 0 Margins.Top = 0 Margins.Right = 0 @@ -1060,6 +1104,11 @@ object connform: Tconnform object Filter1: TMenuItem Action = actFilter end + object menuFoldersAtTop: TMenuItem + AutoCheck = True + Caption = 'Folders at top' + OnClick = menuFoldersAtTopClick + end end object TimerStatistics: TTimer Interval = 60000 diff --git a/source/connections.pas b/source/connections.pas index bddb68ff5..7ddee4b15 100644 --- a/source/connections.pas +++ b/source/connections.pas @@ -12,7 +12,8 @@ interface Winapi.Windows, System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ComCtrls, VirtualTrees, Vcl.Menus, Vcl.Graphics, System.Generics.Collections, Winapi.ActiveX, extra_controls, Winapi.Messages, dbconnection, gnugettext, SynRegExpr, System.Types, Vcl.GraphUtil, Data.Win.ADODB, System.StrUtils, - System.Math, System.Actions, System.IOUtils, Vcl.ActnList, Vcl.StdActns; + System.Math, System.Actions, System.IOUtils, Vcl.ActnList, Vcl.StdActns, VirtualTrees.BaseTree, VirtualTrees.Types, VirtualTrees.EditLink, + VirtualTrees.BaseAncestorVCL, VirtualTrees.AncestorVCL, dbstructures; type Tconnform = class(TExtForm) @@ -21,6 +22,7 @@ Tconnform = class(TExtForm) btnSave: TButton; btnNew: TButton; btnDelete: TButton; + chkForceUnicode: TCheckBox; popupSessions: TPopupMenu; menuSave: TMenuItem; menuDelete: TMenuItem; @@ -37,7 +39,7 @@ Tconnform = class(TExtForm) editPort: TEdit; updownPort: TUpDown; editPassword: TEdit; - editUsername: TEdit; + editUsername: TButtonedEdit; editHost: TButtonedEdit; tabAdvanced: TTabSheet; tabStatistics: TTabSheet; @@ -137,6 +139,9 @@ Tconnform = class(TExtForm) timerEditFilterDelay: TTimer; comboSSHExe: TComboBox; chkSSHActive: TCheckBox; + comboSSLVerification: TComboBox; + lblSSLVerification: TLabel; + menuFoldersAtTop: TMenuItem; procedure FormCreate(Sender: TObject); procedure btnOpenClick(Sender: TObject); procedure FormShow(Sender: TObject); @@ -178,7 +183,7 @@ Tconnform = class(TExtForm) Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode; var Effect: Integer; var Accept: Boolean); procedure ListSessionsDragDrop(Sender: TBaseVirtualTree; Source: TObject; - DataObject: IDataObject; Formats: TFormatArray; Shift: TShiftState; + DataObject: TVTDragDataObject; Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode); procedure btnMoreClick(Sender: TObject); procedure menuRenameClick(Sender: TObject); @@ -200,6 +205,10 @@ Tconnform = class(TExtForm) procedure timerEditFilterDelayTimer(Sender: TObject); procedure chkSSHActiveClick(Sender: TObject); procedure PageControlDetailsChange(Sender: TObject); + procedure editUsernameRightButtonClick(Sender: TObject); + procedure ListSessionsCompareNodes(Sender: TBaseVirtualTree; Node1, + Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer); + procedure menuFoldersAtTopClick(Sender: TObject); private { Private declarations } FLoaded: Boolean; @@ -207,6 +216,7 @@ Tconnform = class(TExtForm) FServerVersion: String; FSettingsImportWaitTime: Cardinal; FPopupDatabases: TPopupMenu; + FPopupCiphers: TPopupMenu; FButtonAnimationStep: Integer; FLastSelectedNetTypeGroup: TNetTypeGroup; function GetSelectedNetType: TNetType; @@ -219,6 +229,7 @@ Tconnform = class(TExtForm) function NodeSessionNames(Node: PVirtualNode; var RegKey: String): TStringList; function GetWindowCaption: String; procedure MenuDatabasesClick(Sender: TObject); + procedure MenuCiphersClick(Sender: TObject); procedure WMNCLBUTTONDOWN(var Msg: TWMNCLButtonDown) ; message WM_NCLBUTTONDOWN; procedure WMNCLBUTTONUP(var Msg: TWMNCLButtonUp) ; message WM_NCLBUTTONUP; procedure RefreshBackgroundColors; @@ -230,7 +241,7 @@ Tconnform = class(TExtForm) implementation -uses Main, apphelpers, grideditlinks; +uses Main, apphelpers, grideditlinks, dbstructures.sqlite; {$I const.inc} @@ -279,11 +290,12 @@ procedure Tconnform.FormCreate(Sender: TObject); HasSizeGrip := True; Caption := GetWindowCaption; - ListSessions.OnCompareNodes := MainForm.AnyGridCompareNodes; + FixVT(ListSessions); ListSessions.OnHeaderClick := MainForm.AnyGridHeaderClick; ListSessions.OnHeaderDraggedOut := MainForm.AnyGridHeaderDraggedOut; btnImportSettings.Caption := MainForm.actImportSettings.Caption; FLoaded := False; + menuFoldersAtTop.Checked := AppSettings.ReadBool(asSessionManagerListFoldersAtTop); comboNetType.Clear; Params := TConnectionParameters.Create; @@ -311,7 +323,7 @@ procedure Tconnform.FormCreate(Sender: TObject); editLogFilePath.Hint := FilenameHint; // Populate dropdown with supported SSH executables - ExeFiles := TDirectory.GetFiles(ExtractFilePath(ParamStr(0)), '*.exe'); + ExeFiles := TDirectory.GetFiles(GetAppDir, '*.exe'); for ExePath in ExeFiles do begin ExeFile := ExtractFileName(ExePath); if ExecRegExprI('([pk]link|putty)', ExeFile) then begin @@ -343,11 +355,17 @@ procedure Tconnform.RefreshSessions(ParentNode: PVirtualNode); Params := TConnectionParameters.Create(RegKey+SessionNames[i]); SessNode := ListSessions.AddChild(ParentNode, PConnectionParameters(Params)); if Params.IsFolder then begin + SessNode.Dummy := 1; // We use this Byte value later in CompareNodes RefreshSessions(SessNode); + end + else begin + SessNode.Dummy := 0; end; end; - if not Assigned(ParentNode) then + if not Assigned(ParentNode) then begin RefreshBackgroundColors; + ListSessions.SortTree(ListSessions.Header.SortColumn, ListSessions.Header.SortDirection); + end; end; @@ -392,7 +410,6 @@ procedure Tconnform.FormShow(Sender: TObject); MakeFullyVisible; pnlLeft.Width := AppSettings.ReadIntDpiAware(asSessionManagerListWidth, Self); splitterMain.OnMoved(Sender); - FixVT(ListSessions); RestoreListSetup(ListSessions); // Init sessions tree @@ -480,6 +497,7 @@ procedure Tconnform.btnSaveClick(Sender: TObject); Sess.LoginPrompt := chkLoginPrompt.Checked; Sess.WindowsAuth := chkWindowsAuth.Checked; Sess.CleartextPluginEnabled := chkCleartextPluginEnabled.Checked; + Sess.ForceUnicode := chkForceUnicode.Checked; Sess.Port := updownPort.Position; Sess.NetType := SelectedNetType; Sess.Compressed := chkCompressed.Checked; @@ -506,6 +524,7 @@ procedure Tconnform.btnSaveClick(Sender: TObject); Sess.SSLCertificate := editSSLCertificate.Text; Sess.SSLCACertificate := editSSLCACertificate.Text; Sess.SSLCipher := editSSLCipher.Text; + Sess.SSLVerification := comboSSLVerification.ItemIndex; Sess.IgnoreDatabasePattern := editIgnoreDatabasePattern.Text; Sess.LogFileDdl := chkLogFileDdl.Checked; Sess.LogFileDml := chkLogFileDml.Checked; @@ -708,6 +727,7 @@ function Tconnform.CurrentParams: TConnectionParameters; Result.LoginPrompt := chkLoginPrompt.Checked; Result.WindowsAuth := chkWindowsAuth.Checked; Result.CleartextPluginEnabled := chkCleartextPluginEnabled.Checked; + Result.ForceUnicode := chkForceUnicode.Checked; if updownPort.Enabled then Result.Port := updownPort.Position else @@ -729,6 +749,7 @@ function Tconnform.CurrentParams: TConnectionParameters; Result.SSLCertificate := editSSLCertificate.Text; Result.SSLCACertificate := editSSLCACertificate.Text; Result.SSLCipher := editSSLCipher.Text; + Result.SSLVerification := comboSSLVerification.ItemIndex; Result.StartupScriptFilename := editStartupScript.Text; Result.Compressed := chkCompressed.Checked; Result.QueryTimeout := updownQueryTimeout.Position; @@ -795,14 +816,15 @@ procedure Tconnform.ListSessionsGetText(Sender: TBaseVirtualTree; CellText := CellText + ' *'; end; 1: CellText := Sess.Hostname; - 2: CellText := Sess.Username; - 3: CellText := Sess.ServerVersion; - 4: if Sess.LastConnect>0 then + 2: CellText := Sess.Port.ToString; + 3: CellText := Sess.Username; + 4: CellText := Sess.ServerVersion; + 5: if Sess.LastConnect>0 then CellText := DateTimeToStr(Sess.LastConnect) else CellText := ''; - 5: CellText := FormatNumber(Sess.Counter); - 6: CellText := Sess.Comment; + 6: CellText := FormatNumber(Sess.Counter); + 7: CellText := Sess.Comment; end; end; end; @@ -849,6 +871,25 @@ procedure Tconnform.ListSessionsBeforeCellPaint(Sender: TBaseVirtualTree; end; end; +procedure Tconnform.ListSessionsCompareNodes(Sender: TBaseVirtualTree; Node1, + Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer); +var + VT: TVirtualStringTree; + DirectionMarker: Integer; +begin + VT := Sender as TVirtualStringTree; + if Assigned(Node1) and Assigned(Node2) then begin + // This marker when set to -1 ensures folders are at the top + DirectionMarker := IfThen(VT.Header.SortDirection = sdAscending, 1, -1); + if menuFoldersAtTop.Checked and (Node1.Dummy=1) and (Node2.Dummy<>1) then + Result := -1 * DirectionMarker + else if menuFoldersAtTop.Checked and (Node1.Dummy<>1) and (Node2.Dummy=1) then + Result := 1 * DirectionMarker + else + Result := CompareAnyNode(VT.Text[Node1, Column], VT.Text[Node2, Column]); + end; +end; + procedure Tconnform.ListSessionsCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink); begin @@ -858,7 +899,7 @@ procedure Tconnform.ListSessionsCreateEditor(Sender: TBaseVirtualTree; Node: PVi procedure Tconnform.ListSessionsDragDrop(Sender: TBaseVirtualTree; - Source: TObject; DataObject: IDataObject; Formats: TFormatArray; + Source: TObject; DataObject: TVTDragDataObject; Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode); var TargetNode, ParentNode: PVirtualNode; @@ -923,9 +964,11 @@ procedure Tconnform.ListSessionsDragOver(Sender: TBaseVirtualTree; TargetSess := Sender.GetNodeData(TargetNode); Accept := (Source = Sender) and Assigned(TargetSess) - and (Mode <> dmNowhere) - and (TargetNode <> ListSessions.FocusedNode.Parent); - + and (Mode <> dmNowhere); + if Accept and (Mode = dmOnNode) and (TargetNode = ListSessions.FocusedNode.Parent) then + Accept := False; + if Accept and (Mode in [dmAbove, dmBelow]) and (TargetNode.Parent = ListSessions.FocusedNode.Parent) then + Accept := False; // Moving a folder into itself would create an infinite folder structure if Accept and TargetSess.IsFolder then Accept := Accept and (TargetNode <> ListSessions.FocusedNode); @@ -975,6 +1018,7 @@ procedure Tconnform.ListSessionsFocusChanged(Sender: TBaseVirtualTree; menuNewSessionInFolder.Enabled := InFolder; menuNewFolderInFolder.Enabled := InFolder; FreeAndNil(FPopupDatabases); + FreeAndNil(FPopupCiphers); if not SessionFocused then begin PageControlDetails.ActivePage := tabStart; @@ -995,6 +1039,7 @@ procedure Tconnform.ListSessionsFocusChanged(Sender: TBaseVirtualTree; chkLoginPrompt.Checked := Sess.LoginPrompt; chkWindowsAuth.Checked := Sess.WindowsAuth; chkCleartextPluginEnabled.Checked := Sess.CleartextPluginEnabled; + chkForceUnicode.Checked := Sess.ForceUnicode; updownPort.Position := Sess.Port; chkCompressed.Checked := Sess.Compressed; updownQueryTimeout.Position := Sess.QueryTimeout; @@ -1028,6 +1073,7 @@ procedure Tconnform.ListSessionsFocusChanged(Sender: TBaseVirtualTree; editSSLCertificate.Text := Sess.SSLCertificate; editSSLCACertificate.Text := Sess.SSLCACertificate; editSSLCipher.Text := Sess.SSLCipher; + comboSSLVerification.ItemIndex := Sess.SSLVerification; editIgnoreDatabasePattern.Text := Sess.IgnoreDatabasePattern; chkLogFileDdl.Checked := Sess.LogFileDdl; chkLogFileDml.Checked := Sess.LogFileDml; @@ -1118,6 +1164,9 @@ procedure Tconnform.ListSessionsNewText(Sender: TBaseVirtualTree; SiblingSessions := NodeSessionNames(Node.Parent, ParentKey); + // Safety replacement for folder separator, see issue #682 + NewText := StringReplace(NewText, '\', '-', [rfReplaceAll]); + if SiblingSessions.IndexOf(NewText) > -1 then begin ErrorDialog( f_('Session "%s" already exists!', [ParentKey+NewText]) @@ -1269,7 +1318,7 @@ procedure Tconnform.editDatabasesRightButtonClick(Sender: TObject); Item.Caption := DB; Item.OnClick := MenuDatabasesClick; Item.AutoCheck := True; - Item.RadioItem := Params.NetTypeGroup = ngPgSQL; + Item.RadioItem := Params.IsAnyPostgreSQL; FPopupDatabases.Items.Add(Item); end; Databases.Free; @@ -1309,6 +1358,52 @@ procedure Tconnform.MenuDatabasesClick(Sender: TObject); end; +procedure Tconnform.MenuCiphersClick(Sender: TObject); +begin + editUsername.Text := TMenuItem(Sender).Caption; +end; + + +procedure Tconnform.editUsernameRightButtonClick(Sender: TObject); +var + Params: TConnectionParameters; + Item: TMenuItem; + LibraryPath: String; + Lib: TSQLiteLib; + p: TPoint; + i: Integer; +begin + // Provide supported cipher names + if FPopupCiphers = nil then begin + FPopupCiphers := TPopupMenu.Create(Self); + FPopupCiphers.AutoHotkeys := maManual; + Params := CurrentParams; + LibraryPath := GetAppDir + Params.LibraryOrProvider; + // Throws EDbError on any failure: + Lib := TSQLiteLib.CreateWithMultipleCipherFunctions(LibraryPath, Params.DefaultLibrary); + for i:=1 to Lib.sqlite3mc_cipher_count() do begin + Item := TMenuItem.Create(FPopupCiphers); + Item.Caption := Utf8ToString(Lib.sqlite3mc_cipher_name(i)); + Item.OnClick := MenuCiphersClick; + FPopupCiphers.Items.Add(Item); + end; + Lib.Free; + + end; + + p := editUsername.ClientToScreen(editUsername.ClientRect.BottomRight); + FPopupCiphers.Popup(p.X-editUsername.Images.Width, p.Y); +end; + + +procedure Tconnform.menuFoldersAtTopClick(Sender: TObject); +begin + AppSettings.WriteBool(asSessionManagerListFoldersAtTop, menuFoldersAtTop.Checked); + ListSessions.SortTree(ListSessions.Header.SortColumn, ListSessions.Header.SortDirection); + if ListSessions.SelectedCount > 0 then + ListSessions.ScrollIntoView(ListSessions.GetFirstSelected, False); +end; + procedure Tconnform.menuRenameClick(Sender: TObject); begin // Start node editor to rename a session @@ -1319,6 +1414,7 @@ procedure Tconnform.menuRenameClick(Sender: TObject); procedure Tconnform.comboNetTypeChange(Sender: TObject); var Params: TConnectionParameters; + Libs: TStringList; begin // Autoset default connection data as long as that was not modified by user // and only if net type group has now changed @@ -1337,8 +1433,14 @@ procedure Tconnform.comboNetTypeChange(Sender: TObject); if not editHost.Modified then editHost.Text := Params.DefaultHost; chkSSHActive.Checked := Params.DefaultSshActive; + end; - comboLibrary.Items := Params.GetLibraries; + // Populate libraries combobox. Required on each net group change, and also between + // SQLite and SQLite-encrypted. + Libs := Params.GetLibraries; + mainform.LogSQL(Libs.CommaText); + if Libs.Text <> comboLibrary.Items.Text then begin + comboLibrary.Items := Libs; comboLibrary.ItemIndex := comboLibrary.Items.IndexOf(Params.DefaultLibrary); end; @@ -1364,6 +1466,7 @@ procedure Tconnform.Modification(Sender: TObject); or (Sess.LoginPrompt <> chkLoginPrompt.Checked) or (Sess.WindowsAuth <> chkWindowsAuth.Checked) or (Sess.CleartextPluginEnabled <> chkCleartextPluginEnabled.Checked) + or (Sess.ForceUnicode <> chkForceUnicode.Checked) or (Sess.Port <> updownPort.Position) or (Sess.Compressed <> chkCompressed.Checked) or (Sess.QueryTimeout <> updownQueryTimeout.Position) @@ -1390,6 +1493,7 @@ procedure Tconnform.Modification(Sender: TObject); or (Sess.SSLCertificate <> editSSLCertificate.Text) or (Sess.SSLCACertificate <> editSSLCACertificate.Text) or (Sess.SSLCipher <> editSSLCipher.Text) + or (Sess.SSLVerification <> comboSSLVerification.ItemIndex) or (Sess.IgnoreDatabasePattern <> editIgnoreDatabasePattern.Text) or (Sess.LogFileDdl <> chkLogFileDdl.Checked) or (Sess.LogFileDml <> chkLogFileDml.Checked) @@ -1400,7 +1504,8 @@ procedure Tconnform.Modification(Sender: TObject); FSessionModified := FSessionModified or PasswordModified; if (Sender=editHost) or (Sender=editUsername) or (Sender=editPassword) or (Sender=comboNetType) or (Sender=chkWindowsAuth) or (Sender=editPort) or - (Sender=chkCleartextPluginEnabled) then begin + (Sender=chkCleartextPluginEnabled) or (Sender=chkForceUnicode) + then begin // Be sure to use the modified connection params next time the user clicks the "Databases" pulldown FreeAndNil(FPopupDatabases); end; @@ -1458,16 +1563,26 @@ procedure Tconnform.ValidateControls; if SessionFocused then begin // Validate session GUI stuff on "Settings" tab: + lblHost.Caption := _('Hostname / IP:'); + lblUsername.Caption := _('User')+':'; + lblPassword.Caption := _('Password:'); + lblDatabase.Caption := _('Databases')+':'; + editDatabases.TextHint := _('Separated by semicolon'); case Params.NetType of ntMySQL_NamedPipe: begin lblHost.Caption := _('Socket name:'); end; - ntSQLite: begin + ntPgSQL_TCPIP, ntPgSQL_SSHtunnel: begin + lblDatabase.Caption := _('Database')+':'; + editDatabases.TextHint := _('Single database name'); + end; + ntSQLite, ntSQLiteEncrypted: begin lblHost.Caption := _('Database filename(s)')+':'; + lblUsername.Caption := _('Cipher')+':'; + lblPassword.Caption := _('Key:'); + lblDatabase.Caption := _('Encryption parameters')+':'; + editDatabases.TextHint := _('Example:') + ' kdf_iter=4000;legacy=1;...'; end - else begin - lblHost.Caption := _('Hostname / IP:'); - end; end; editHost.RightButton.Visible := Params.IsAnySQLite; chkLoginPrompt.Enabled := Params.NetTypeGroup in [ngMySQL, ngMSSQL, ngPgSQL]; @@ -1475,17 +1590,19 @@ procedure Tconnform.ValidateControls; lblUsername.Enabled := (Params.NetTypeGroup in [ngMySQL, ngMSSQL, ngPgSQL, ngInterbase]) and ((not chkLoginPrompt.Checked) or (not chkLoginPrompt.Enabled)) and ((not chkWindowsAuth.Checked) or (not chkWindowsAuth.Enabled)); + lblUsername.Enabled := lblUsername.Enabled or (Params.NetType = ntSQLiteEncrypted); editUsername.Enabled := lblUsername.Enabled; + editUsername.RightButton.Visible := Params.NetType = ntSQLiteEncrypted; lblPassword.Enabled := lblUsername.Enabled; editPassword.Enabled := lblUsername.Enabled; lblPort.Enabled := Params.NetType in [ntMySQL_TCPIP, ntMySQL_SSHtunnel, ntMySQL_ProxySQLAdmin, ntMySQL_RDS, ntMSSQL_TCPIP, ntPgSQL_TCPIP, ntPgSQL_SSHtunnel, ntInterbase_TCPIP, ntFirebird_TCPIP]; editPort.Enabled := lblPort.Enabled; updownPort.Enabled := lblPort.Enabled; chkCompressed.Enabled := Params.IsAnyMySQL; - lblDatabase.Caption := IfThen(Params.IsAnyPostgreSQL, _('Database')+':', _('Databases')+':'); - editDatabases.TextHint := IfThen(Params.IsAnyPostgreSQL, _('Single database name'), _('Separated by semicolon')); lblDatabase.Enabled := Params.NetTypeGroup in [ngMySQL, ngMSSQL, ngPgSQL, ngInterbase]; + lblDatabase.Enabled := lblDatabase.Enabled or (Params.NetType = ntSQLiteEncrypted); editDatabases.Enabled := lblDatabase.Enabled; + editDatabases.RightButton.Visible := Params.NetTypeGroup in [ngMySQL, ngMSSQL, ngPgSQL, ngInterbase]; // SSH tunnel tab: chkSSHActive.Enabled := Params.SshSupport; lblSSHExe.Enabled := Params.SSHActive; @@ -1505,6 +1622,15 @@ procedure Tconnform.ValidateControls; lblSSHLocalPort.Enabled := Params.SSHActive; editSSHlocalport.Enabled := Params.SSHActive; // Advanced tab: + lblQueryTimeout.Enabled := True; + editQueryTimeout.Enabled := lblQueryTimeout.Enabled; + updownQueryTimeout.Enabled := lblQueryTimeout.Enabled; + chkLocalTimeZone.Enabled := Params.NetTypeGroup = ngMySQL; + chkFullTableStatus.Enabled := (Params.NetTypeGroup in [ngMySQL, ngPgSQL, ngSQLite]) and (Params.NetType <> ntMySQL_ProxySQLAdmin); + chkCleartextPluginEnabled.Enabled := Params.NetTypeGroup = ngMySQL; + chkForceUnicode.Enabled := Params.NetTypeGroup = ngMySQL; + editLogFilePath.Enabled := Params.LogFileDdl or Params.LogFileDml; + // SSL tab: chkWantSSL.Enabled := Params.NetType in [ntMySQL_TCPIP, ntMySQL_SSHtunnel, ntMySQL_ProxySQLAdmin, ntMySQL_RDS, ntPgSQL_TCPIP, ntPgSQL_SSHtunnel]; lblSSLPrivateKey.Enabled := Params.WantSSL; editSSLPrivateKey.Enabled := Params.WantSSL; @@ -1514,13 +1640,8 @@ procedure Tconnform.ValidateControls; editSSLCertificate.Enabled := Params.WantSSL; lblSSLcipher.Enabled := Params.WantSSL; editSSLcipher.Enabled := Params.WantSSL; - lblQueryTimeout.Enabled := True; - editQueryTimeout.Enabled := lblQueryTimeout.Enabled; - updownQueryTimeout.Enabled := lblQueryTimeout.Enabled; - chkLocalTimeZone.Enabled := Params.NetTypeGroup = ngMySQL; - chkFullTableStatus.Enabled := (Params.NetTypeGroup in [ngMySQL, ngPgSQL]) and (Params.NetType <> ntMySQL_ProxySQLAdmin); - chkCleartextPluginEnabled.Enabled := Params.NetTypeGroup = ngMySQL; - editLogFilePath.Enabled := Params.LogFileDdl or Params.LogFileDml; + lblSSLVerification.Enabled := Params.WantSSL; + comboSSLVerification.Enabled := Params.WantSSL; Params.Free; end; @@ -1624,7 +1745,7 @@ procedure Tconnform.PickFile(Sender: TObject); FileNames.Assign(Selector.Files); for i:=0 to FileNames.Count-1 do begin // Remove path if it's the application directory - if ExtractFilePath(FileNames[i]) = ExtractFilePath(Application.ExeName) then + if ExtractFilePath(FileNames[i]) = GetAppDir then FileNames[i] := ExtractFileName(FileNames[i]); end; Edit.Text := Implode(DELIM, FileNames); diff --git a/source/const.inc b/source/const.inc index 3747a8403..e89efdbf3 100644 --- a/source/const.inc +++ b/source/const.inc @@ -33,6 +33,7 @@ const ICONINDEX_UNIQUEKEY = 24; ICONINDEX_FULLTEXTKEY = 22; ICONINDEX_SPATIALKEY = 126; + ICONINDEX_VECTORKEY = 207; ICONINDEX_FOREIGNKEY = 136; ICONINDEX_SERVER = 36; ICONINDEX_DB = 5; @@ -45,6 +46,8 @@ const ICONINDEX_FUNCTION = 13; ICONINDEX_EVENT = 80; ICONINDEX_KEYWORD = 25; + ICONINDEX_USER = 43; + ICONINDEX_ROLE = 95; // Size of byte units {Kibibyte} SIZE_KB = Int64(1024); @@ -95,8 +98,14 @@ const MsgUnhandledNetType: String = 'Unhandled connection type (%d)'; MsgUnhandledControl: String = 'Unhandled control in %s'; MsgDisconnect: String = 'Connection to %s closed at %s'; - MsgInvalidColumn: String = 'Column #%d not available. Query returned %d columns and %d rows.'; + // This must be an empty string, otherwise TTableColumn's get GenerationExpression=XYZ on old servers + TextInvalidColumn: String = ''; FILEFILTER_SQLITEDB = '*.sqlite3;*.sqlite;*.db;*.s3db'; FILEEXT_SQLITEDB = 'sqlite3'; + FILEEXT_SNIPPET = '.sql'; PROPOSAL_ITEM_HEIGHT = 18; + // Note the following should be in sync to what MySQL returns from SHOW WARNINGS + SLogPrefixWarning = 'Warning'; + SLogPrefixNote = 'Note'; + SLogPrefixInfo = 'Info'; diff --git a/source/copytable.pas b/source/copytable.pas index 9be282743..af3e8aee8 100644 --- a/source/copytable.pas +++ b/source/copytable.pas @@ -5,7 +5,8 @@ interface uses Winapi.Windows, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, extra_controls, - dbconnection, dbstructures, dbstructures.mysql, VirtualTrees, SynEdit, SynMemo, Vcl.Menus, gnugettext; + dbconnection, dbstructures, dbstructures.mysql, VirtualTrees, SynEdit, SynMemo, Vcl.Menus, gnugettext, VirtualTrees.BaseTree, VirtualTrees.Types, + VirtualTrees.BaseAncestorVCL, VirtualTrees.AncestorVCL; type TCopyTableForm = class(TExtForm) @@ -356,12 +357,13 @@ procedure TCopyTableForm.btnOKClick(Sender: TObject); Column: TTableColumn; Key: TTableKey; ForeignKey: TForeignKey; -const - ClausePattern: String = #9 + '%s,' + CRLF; + ClausePattern: String; + NewObj: TDBObject; begin // Compose and run CREATE query TargetTable := FConnection.QuotedDbAndTableName(comboDatabase.Text, editNewTablename.Text); + ClausePattern := CodeIndent + '%s,' + sLineBreak; // Watch out if target table exists try @@ -376,6 +378,7 @@ procedure TCopyTableForm.btnOKClick(Sender: TObject); Exit; end; FConnection.Query('DROP TABLE '+TargetTable); + FConnection.ShowWarnings; end; Screen.Cursor := crHourglass; @@ -413,7 +416,7 @@ procedure TCopyTableForm.btnOKClick(Sender: TObject); for Column in SelectedColumns do begin AutoIncGetsKey := False; AutoIncRemoved := False; - AutoIncName := Column.AutoIncName; + AutoIncName := FConnection.SqlProvider.GetSql(qAutoInc); if Column.DefaultType = cdtAutoInc then begin for Key in SelectedKeys do begin // Don't check index type, MySQL allows auto-increment columns on nearly all indexes @@ -478,11 +481,20 @@ procedure TCopyTableForm.btnOKClick(Sender: TObject); try MainForm.ShowStatusMsg(_('Creating table ...')); FConnection.Query(CreateCode); - if InsertCode <> '' then + FConnection.ShowWarnings; + if InsertCode <> '' then begin FConnection.Query(InsertCode); + FConnection.ShowWarnings; + end; // actRefresh takes care of whether the table editor is open // See also issue #1597 - MainForm.actRefresh.Execute + MainForm.actRefresh.Execute; + // Select it in tree + NewObj := TDBObject.Create(FDBObj.Connection); + NewObj.NodeType := lntTable; + NewObj.Database := comboDatabase.Text; + NewObj.Name := editNewTablename.Text; + MainForm.ActiveDbObj := NewObj; except on E:EDbError do begin Screen.Cursor := crDefault; diff --git a/source/createdatabase.pas b/source/createdatabase.pas index 14107bd0a..2f71279b1 100644 --- a/source/createdatabase.pas +++ b/source/createdatabase.pas @@ -52,12 +52,15 @@ procedure TCreateDatabaseForm.FormCreate(Sender: TObject); } procedure TCreateDatabaseForm.FormShow(Sender: TObject); var - ServerCollation, Collation, Charset, CreateCode: String; + ServerCollation, PreviousCollation, CurrentCollation: String; + Charset, CreateCode: String; CollationTable: TDBQuery; rx: TRegExpr; begin FConnection := MainForm.ActiveConnection; CollationTable := FConnection.CollationTable; + CurrentCollation := ''; + PreviousCollation := AppSettings.ReadString(asCreateDbCollation); // Detect servers default collation case FConnection.Parameters.NetTypeGroup of @@ -68,13 +71,9 @@ procedure TCreateDatabaseForm.FormShow(Sender: TObject); end; lblServerDefaultCollation.Caption := f_('Servers default: %s', [ServerCollation]); - if modifyDB = '' then begin + if modifyDB.IsEmpty then begin Caption := _('Create database ...'); editDBName.Text := ''; - Charset := ''; - Collation := AppSettings.ReadString(asCreateDbCollation); - if Collation.IsEmpty then - Collation := ServerCollation; end else begin Caption := _('Alter database ...'); @@ -88,19 +87,19 @@ procedure TCreateDatabaseForm.FormShow(Sender: TObject); Charset := rx.Match[1]; rx.Expression := '\sCOLLATE\s+(\w+)\b'; if rx.Exec(CreateCode) then - Collation := rx.Match[1]; + CurrentCollation := rx.Match[1]; rx.Free; // Find default collation of given charset - if (Collation = '') and (Charset <> '') and Assigned(CollationTable) then begin + if (CurrentCollation = '') and (Charset <> '') and Assigned(CollationTable) then begin while not CollationTable.Eof do begin if (CollationTable.Col('Charset') = Charset) and (LowerCase(CollationTable.Col('Default')) = 'yes') then - Collation := CollationTable.Col('Collation'); + CurrentCollation := CollationTable.Col('Collation'); CollationTable.Next; end; end; end; - // Select collation in pulldown + // Populate collation combo box comboCollation.Enabled := Assigned(CollationTable); lblCollation.Enabled := comboCollation.Enabled; comboCollation.Clear; @@ -110,9 +109,16 @@ procedure TCreateDatabaseForm.FormShow(Sender: TObject); comboCollation.Items.Add(CollationTable.Col('Collation')); CollationTable.Next; end; - comboCollation.ItemIndex := comboCollation.Items.IndexOf(Collation); + // Pre-select best fitting collation + comboCollation.ItemIndex := comboCollation.Items.IndexOf(CurrentCollation); if comboCollation.ItemIndex = -1 then - comboCollation.ItemIndex := 0; + comboCollation.ItemIndex := comboCollation.Items.IndexOf(PreviousCollation); + if comboCollation.ItemIndex = -1 then + comboCollation.ItemIndex := comboCollation.Items.IndexOf(ServerCollation); + if comboCollation.ItemIndex = -1 then + comboCollation.ItemIndex := comboCollation.Items.IndexOf('utf8mb4_unicode_ci'); + if comboCollation.ItemIndex = -1 then + comboCollation.ItemIndex := 0; // give up, use the first one end; editDBName.SetFocus; @@ -135,9 +141,10 @@ procedure TCreateDatabaseForm.btnOKClick(Sender: TObject); ObjectsInNewDb, ObjectsInOldDb: TDBObjectList; i, j: Integer; begin - if modifyDB = '' then try + if modifyDB.IsEmpty then try sql := GetCreateStatement; FConnection.Query(sql); + FConnection.ShowWarnings; AppSettings.WriteString(asCreateDbCollation, comboCollation.Text); MainForm.RefreshTree; // Close form @@ -154,6 +161,7 @@ procedure TCreateDatabaseForm.btnOKClick(Sender: TObject); if modifyDB = editDBName.Text then begin // Alter database FConnection.Query(sql); + FConnection.ShowWarnings; end else begin // Rename database ObjectsInOldDb := FConnection.GetDBObjects(modifyDB, True); @@ -180,6 +188,7 @@ procedure TCreateDatabaseForm.btnOKClick(Sender: TObject); if AllDatabases.IndexOf(editDBName.Text) = -1 then begin // Target db does not exist - create it FConnection.Query(GetCreateStatement); + FConnection.ShowWarnings; end else begin if MessageDialog(f_('Database "%s" exists. But it does not contain objects with same names as in "%s", so it''s uncritical to move everything. Move all objects to "%s"?', [editDBName.Text, modifyDB, editDBName.Text]), mtConfirmation, [mbYes, mbCancel]) <> mrYes then @@ -195,6 +204,7 @@ procedure TCreateDatabaseForm.btnOKClick(Sender: TObject); Delete(sql, Length(sql)-1, 2); sql := 'RENAME TABLE '+sql; FConnection.Query(sql); + FConnection.ShowWarnings; FConnection.ClearDbObjects(modifyDB); FConnection.ClearDbObjects(editDBName.Text); end; @@ -202,6 +212,7 @@ procedure TCreateDatabaseForm.btnOKClick(Sender: TObject); ObjectsLeft := FConnection.GetDBObjects(modifyDB); if ObjectsLeft.Count = 0 then begin FConnection.Query('DROP DATABASE '+FConnection.QuoteIdent(modifyDB)); + FConnection.ShowWarnings; MainForm.RefreshTree; end; end; diff --git a/source/csv_detector.pas b/source/csv_detector.pas index 9eb424c76..ade43965d 100644 --- a/source/csv_detector.pas +++ b/source/csv_detector.pas @@ -359,7 +359,7 @@ function TfrmCsvDetector.ComposeCreateStatement(Columns: TTableColumnList): Stri TableName := FConnection.CleanIdent(TableName); Result := 'CREATE TABLE '+FConnection.QuoteIdent(FLoadDataFrm.comboDatabase.Text)+'.'+FConnection.QuoteIdent(TableName)+' (' + sLineBreak; for Col in Columns do begin - Result := Result + #9 + Col.SQLCode; + Result := Result + CodeIndent + Col.SQLCode; if Col <> Columns.Last then Result := Result + ','; Result := Result + sLineBreak; @@ -377,6 +377,7 @@ procedure TfrmCsvDetector.btnSaveClick(Sender: TObject); try Screen.Cursor := crHourGlass; FConnection.Query(SynMemoCreateTable.Text); + FConnection.ShowWarnings; ModalResult := mrOk; rx := TRegExpr.Create; rx.ModifierI := True; diff --git a/source/data_sorting.pas b/source/data_sorting.pas index 6baaea390..935566134 100644 --- a/source/data_sorting.pas +++ b/source/data_sorting.pas @@ -4,7 +4,7 @@ interface uses Winapi.Windows, System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ComCtrls, Vcl.Buttons, - Vcl.Graphics, apphelpers, gnugettext, extra_controls; + Vcl.Graphics, apphelpers, gnugettext, extra_controls, dbconnection; type @@ -26,6 +26,9 @@ TfrmDataSorting = class(TExtForm) FColumnNames: TStringList; FSortItems: TSortItems; FOldOrderClause: String; + FDeleteTimer: TTimer; + FDeleteButtonPressed: TButton; + procedure DeleteTimerTimer(Sender: TObject); procedure comboColumnsChange(Sender: TObject); procedure btnOrderClick(Sender: TObject); procedure btnDeleteClick(Sender: TObject); @@ -57,6 +60,11 @@ procedure TfrmDataSorting.FormCreate(Sender: TObject); FSortItems.Assign(MainForm.DataGridSortItems); FOldOrderClause := FSortItems.ComposeOrderClause(MainForm.ActiveConnection); + FDeleteTimer := TTimer.Create(Self); + FDeleteTimer.Interval := 100; + FDeleteTimer.Enabled := False; + FDeleteTimer.OnTimer := DeleteTimerTimer; + // First creation of controls DisplaySortingControls(Sender); end; @@ -236,11 +244,16 @@ procedure TfrmDataSorting.btnOrderClick( Sender: TObject ); Delete order column } procedure TfrmDataSorting.btnDeleteClick(Sender: TObject); -var - btn: TButton; begin - btn := Sender as TButton; - FSortItems.Delete(btn.Tag-1); + FDeleteButtonPressed := Sender as TButton; + FDeleteTimer.Enabled := True; +end; + + +procedure TfrmDataSorting.DeleteTimerTimer(Sender: TObject); +begin + FDeleteTimer.Enabled := False; + FSortItems.Delete(FDeleteButtonPressed.Tag-1); // Refresh controls DisplaySortingControls(Self); // Enables OK button @@ -269,6 +282,7 @@ procedure TfrmDataSorting.btnAddColClick(Sender: TObject); NewSortItem.Column := UnusedColumns[0] else NewSortItem.Column := FColumnNames[0]; + MainForm.LogSQL('Created sorting for column '+NewSortItem.Column+'/'+Integer(NewSortItem.Order).ToString+' in TfrmDataSorting.btnAddColClick', lcDebug); // Refresh controls DisplaySortingControls(Sender); diff --git a/source/dbconnection.pas b/source/dbconnection.pas index 49c50a00b..e39289314 100644 --- a/source/dbconnection.pas +++ b/source/dbconnection.pas @@ -5,7 +5,7 @@ interface uses System.Classes, System.SysUtils, Winapi.Windows, System.Generics.Collections, System.Generics.Defaults, System.DateUtils, System.Types, System.Math, Vcl.Dialogs, Data.Win.ADODB, Data.DB, Data.DBCommon, System.Win.ComObj, Vcl.Graphics, Vcl.ExtCtrls, System.StrUtils, - System.AnsiStrings, Vcl.Controls, Vcl.Forms, System.IOUtils, System.IniFiles, System.Variants, + System.AnsiStrings, Vcl.Controls, Vcl.Forms, System.IOUtils, System.IniFiles, System.Variants, Rtti, SynRegExpr, gnugettext, generic_types, dbstructures, dbstructures.mysql, dbstructures.mssql, dbstructures.postgresql, dbstructures.sqlite, dbstructures.interbase, FireDAC.Stan.Intf, FireDAC.Stan.Option, @@ -29,7 +29,7 @@ TDBQuery = class; TDBQueryList = TObjectList; TDBObject = class; - TColumnPart = (cpAll, cpName, cpType, cpAllowNull, cpDefault, cpVirtuality, cpComment, cpCollation); + TColumnPart = (cpAll, cpName, cpType, cpAllowNull, cpSRID, cpDefault, cpVirtuality, cpComment, cpCollation, cpInvisible); TColumnParts = Set of TColumnPart; TColumnDefaultType = (cdtNothing, cdtText, cdtNull, cdtAutoInc, cdtExpression); // General purpose editing status flag @@ -45,12 +45,13 @@ TTableColumn = class(TPersistent) Name, OldName: String; DataType, OldDataType: TDBDatatype; LengthSet: String; - Unsigned, AllowNull, ZeroFill, LengthCustomized, Invisible: Boolean; + Unsigned, AllowNull, ZeroFill, LengthCustomized, Invisible, Compressed: Boolean; DefaultType: TColumnDefaultType; DefaultText: String; OnUpdateType: TColumnDefaultType; OnUpdateText: String; Comment, Charset, Collation, GenerationExpression, Virtuality: String; + SRID: Cardinal; constructor Create(AOwner: TDBConnection; Serialized: String=''); destructor Destroy; override; procedure Assign(Source: TPersistent); override; @@ -61,12 +62,15 @@ TTableColumn = class(TPersistent) function CastAsText: String; property Status: TEditingStatus read FStatus write SetStatus; property Connection: TDBConnection read FConnection; - function AutoIncName: String; + function FullDataType: String; end; PTableColumn = ^TTableColumn; TTableColumnList = class(TObjectList) public procedure Assign(Source: TTableColumnList); + function FindByName(const Value: String): TTableColumn; + function HasInvisibleColumns: Boolean; + function QuoteIdents: String; end; TColumnCache = TDictionary; @@ -77,19 +81,29 @@ TTableKey = class(TPersistent) UNIQUE = 'UNIQUE'; FULLTEXT = 'FULLTEXT'; SPATIAL = 'SPATIAL'; + VECTOR = 'VECTOR'; private FConnection: TDBConnection; + function GetInsideCreateCode: Boolean; function GetImageIndex: Integer; public Name, OldName: String; IndexType, OldIndexType, Algorithm, Comment: String; - Columns, SubParts: TStringList; - Modified, Added: Boolean; + Columns, SubParts, Collations: TStringList; + Modified, Added, Visible: Boolean; constructor Create(AOwner: TDBConnection); destructor Destroy; override; procedure Assign(Source: TPersistent); override; + function IsPrimary: Boolean; + function IsIndex: Boolean; + function IsUnique: Boolean; + function IsFulltext: Boolean; + function IsSpatial: Boolean; + function IsVector: Boolean; + function IsExpression(KeyPart: Integer): Boolean; procedure Modification(Sender: TObject); - function SQLCode: String; + function SQLCode(TableName: String=''): String; + property InsideCreateCode: Boolean read GetInsideCreateCode; property ImageIndex: Integer read GetImageIndex; property Connection: TDBConnection read FConnection; end; @@ -153,6 +167,7 @@ TDBObject = class(TPersistent) FCreateCodeLoaded: Boolean; FWasSelected: Boolean; FConnection: TDBConnection; + FMap: TStringMap; function GetObjType: String; function GetImageIndex: Integer; function GetOverlayImageIndex: Integer; @@ -168,10 +183,11 @@ TDBObject = class(TPersistent) Rows, Size, Version, AvgRowLen, MaxDataLen, IndexLen, DataLen, DataFree, AutoInc, CheckSum: Int64; // Routine options: Body, Definer, Returns, DataAccess, Security, ArgTypes: String; - Deterministic, RowsAreExact: Boolean; + Deterministic, RowsAreExact, IsMaterialized: Boolean; NodeType, GroupType: TListNodeType; constructor Create(OwnerConnection: TDBConnection); + destructor Destroy; procedure Assign(Source: TPersistent); override; procedure UnloadDetails; procedure Drop; @@ -181,9 +197,10 @@ TDBObject = class(TPersistent) function QuotedDbAndTableName(AlwaysQuote: Boolean=True): String; function QuotedColumn(AlwaysQuote: Boolean=True): String; function SchemaClauseIS(Prefix: String): String; - function RowCount(Reload: Boolean; ForceExact: Bool=False): Int64; + function RowCount(Reload: Boolean; ForceExact: Boolean=False): Int64; function GetCreateCode: String; overload; function GetCreateCode(RemoveAutoInc, RemoveDefiner: Boolean): String; overload; + function AsStringMap: TStringMap; property ObjType: String read GetObjType; property ImageIndex: Integer read GetImageIndex; property OverlayImageIndex: Integer read GetOverlayImageIndex; @@ -282,50 +299,33 @@ TSQLFunctionList = class(TObjectList) { TConnectionParameters and friends } - TNetType = ( - ntMySQL_TCPIP, - ntMySQL_NamedPipe, - ntMySQL_SSHtunnel, - ntMSSQL_NamedPipe, - ntMSSQL_TCPIP, - ntMSSQL_SPX, - ntMSSQL_VINES, - ntMSSQL_RPC, - ntPgSQL_TCPIP, - ntPgSQL_SSHtunnel, - ntSQLite, - ntMySQL_ProxySQLAdmin, - ntInterbase_TCPIP, - ntInterbase_Local, - ntFirebird_TCPIP, - ntFirebird_Local, - ntMySQL_RDS - ); - TNetTypeGroup = (ngMySQL, ngMSSQL, ngPgSQL, ngSQLite, ngInterbase); - TNetGroupLibs = TDictionary; - TConnectionParameters = class(TObject) strict private + FDeleteAfterUse: Boolean; + FLoadedFromSettings: Boolean; FNetType: TNetType; FHostname, FUsername, FPassword, FAllDatabases, FLibraryOrProvider, FComment, FStartupScriptFilename, FSessionPath, FSSLPrivateKey, FSSLCertificate, FSSLCACertificate, FSSLCipher, FServerVersion, FSSHHost, FSSHUser, FSSHPassword, FSSHExe, FSSHPrivateKey, FIgnoreDatabasePattern: String; - FPort, FSSHPort, FSSHLocalPort, FSSHTimeout, FCounter, FQueryTimeout, FKeepAlive: Integer; + FPort, FSSHPort, FSSHLocalPort, FSSHTimeout, FCounter, FQueryTimeout, FKeepAlive, FSSLVerification: Integer; FSSHActive, FLoginPrompt, FCompressed, FLocalTimeZone, FFullTableStatus, - FWindowsAuth, FWantSSL, FIsFolder, FCleartextPluginEnabled: Boolean; + FWindowsAuth, FWantSSL, FIsFolder, FCleartextPluginEnabled, FForceUnicode: Boolean; FSessionColor: TColor; FLastConnect: TDateTime; FLogFileDdl: Boolean; FLogFileDml: Boolean; FLogFilePath: String; - class var FLibraries: TNetGroupLibs; + class var FLibraries: TNetTypeLibs; function GetImageIndex: Integer; function GetSessionName: String; + function GetAllDatabasesList: TStringList; public constructor Create; overload; constructor Create(SessionRegPath: String); overload; + destructor Destroy; override; procedure SaveToRegistry; + property DeleteAfterUse: Boolean read FDeleteAfterUse write FDeleteAfterUse; function CreateConnection(AOwner: TComponent): TDBConnection; function CreateQuery(Connection: TDbConnection): TDBQuery; function NetTypeName(LongFormat: Boolean): String; @@ -375,7 +375,9 @@ TConnectionParameters = class(TObject) property LoginPrompt: Boolean read FLoginPrompt write FLoginPrompt; property WindowsAuth: Boolean read FWindowsAuth write FWindowsAuth; property CleartextPluginEnabled: Boolean read FCleartextPluginEnabled write FCleartextPluginEnabled; + property ForceUnicode: Boolean read FForceUnicode write FForceUnicode; property AllDatabasesStr: String read FAllDatabases write FAllDatabases; + property AllDatabasesList: TStringList read GetAllDatabasesList; property LibraryOrProvider: String read FLibraryOrProvider write FLibraryOrProvider; property Comment: String read FComment write FComment; property StartupScriptFilename: String read FStartupScriptFilename write FStartupScriptFilename; @@ -398,6 +400,7 @@ TConnectionParameters = class(TObject) property SSLCertificate: String read FSSLCertificate write FSSLCertificate; property SSLCACertificate: String read FSSLCACertificate write FSSLCACertificate; property SSLCipher: String read FSSLCipher write FSSLCipher; + property SSLVerification: Integer read FSSLVerification write FSSLVerification; property IgnoreDatabasePattern: String read FIgnoreDatabasePattern write FIgnoreDatabasePattern; property LogFileDdl: Boolean read FLogFileDdl write FLogFileDdl; property LogFileDml: Boolean read FLogFileDml write FLogFileDml; @@ -419,16 +422,20 @@ TDBLogItem = class(TObject) TDBLogEvent = procedure(Msg: String; Category: TDBLogCategory=lcInfo; Connection: TDBConnection=nil) of object; TDBEvent = procedure(Connection: TDBConnection; Database: String) of object; TDBDataTypeArray = Array of TDBDataType; - TSQLSpecifityId = (spDatabaseTable, spDatabaseTableId, spDatabaseDrop, - spDbObjectsTable, spDbObjectsCreateCol, spDbObjectsUpdateCol, spDbObjectsTypeCol, - spEmptyTable, spRenameTable, spRenameView, spCurrentUserHost, spLikeCompare, - spAddColumn, spChangeColumn, spRenameColumn, spForeignKeyEventAction, - spGlobalStatus, spCommandsCounters, spSessionVariables, spGlobalVariables, - spISSchemaCol, - spUSEQuery, spKillQuery, spKillProcess, - spFuncLength, spFuncCeil, spFuncLeft, spFuncNow, spFuncLastAutoIncNumber, - spLockedTables, spDisableForeignKeyChecks, spEnableForeignKeyChecks, - spOrderAsc, spOrderDesc); + TFeatureOrRequirement = ( + frSrid, + frTemporalTypesFraction, + frIntegerDisplayWidth, + frColumnDefaultParentheses, + frEditVariables, + frCreateView, + frCreateProcedure, + frCreateFunction, + frCreateTrigger, + frCreateEvent, + frInvisibleColumns, + frCompressedColumns + ); TDBConnection = class(TComponent) private @@ -437,8 +444,8 @@ TDBConnection = class(TComponent) FServerUptime: Integer; FServerDateTimeOnStartup: String; FParameters: TConnectionParameters; + FOwnsParameters: Boolean; FSecureShellCmd: TSecureShellCmd; - FLoginPromptDone: Boolean; FDatabase: String; FAllDatabases: TStringList; FLogPrefix: String; @@ -475,7 +482,7 @@ TDBConnection = class(TComponent) FQuoteChars: String; FDatatypes: TDBDataTypeArray; FThreadID: Int64; - FSQLSpecifities: Array[TSQLSpecifityId] of String; + FSqlProvider: TSqlProvider; FKeepAliveTimer: TTimer; FFavorites: TStringList; FPrefetchResults: TDBQueryList; @@ -485,6 +492,7 @@ TDBConnection = class(TComponent) FMaxRowsPerInsert: Int64; FCaseSensitivity: Integer; FSQLFunctions: TSQLFunctionList; + FNamedEnums: TStringList; procedure SetActive(Value: Boolean); virtual; abstract; procedure DoBeforeConnect; virtual; procedure StartSSHTunnel(var FinalHost: String; var FinalPort: Integer); @@ -510,10 +518,9 @@ TDBConnection = class(TComponent) function GetCurrentUserHostCombination: String; function GetAllUserHostCombinations: TStringList; function DecodeAPIString(a: AnsiString): String; - function GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; virtual; abstract; + function GetRowCount(Obj: TDBObject; ForceExact: Boolean=False): Int64; virtual; procedure ClearCache(IncludeDBObjects: Boolean); procedure FetchDbObjects(db: String; var Cache: TDBObjectList); virtual; abstract; - procedure SetLockedByThread(Value: TThread); virtual; procedure KeepAliveTimerEvent(Sender: TObject); procedure Drop(Obj: TDBObject); virtual; procedure PrefetchResults(SQL: String); @@ -522,10 +529,14 @@ TDBConnection = class(TComponent) public constructor Create(AOwner: TComponent); override; destructor Destroy; override; - procedure Query(SQL: String; DoStoreResult: Boolean=False; LogCategory: TDBLogCategory=lcSQL); virtual; + procedure Query(SQL: String; DoStoreResult: Boolean=False; LogCategory: TDBLogCategory=lcSQL); overload; virtual; + procedure Query(QueryId: TQueryId); overload; + procedure Query(QueryId: TQueryId; const Args: array of const); overload; procedure Log(Category: TDBLogCategory; Msg: String); function EscapeString(Text: String; ProcessJokerChars: Boolean=False; DoQuote: Boolean=True): String; overload; function EscapeString(Text: String; Datatype: TDBDatatype): String; overload; + function EscapeBin(BinValue: String): String; overload; + function EscapeBin(var ByteData: TBytes): String; overload; function QuoteIdent(Identifier: String; AlwaysQuote: Boolean=True; Glue: Char=#0): String; function DeQuoteIdent(Identifier: String; Glue: Char=#0): String; function CleanIdent(Identifier: String): String; @@ -543,7 +554,7 @@ TDBConnection = class(TComponent) function GetDBObjects(db: String; Refresh: Boolean=False; OnlyNodeType: TListNodeType=lntNone): TDBObjectList; function DbObjectsCached(db: String): Boolean; function ParseDateTime(Str: String): TDateTime; - function GetKeyColumns(Columns: TTableColumnList; Keys: TTableKeyList): TStringList; + function GetKeyColumns(Columns: TTableColumnList; Keys: TTableKeyList): TTableColumnList; function ConnectionInfo: TStringList; virtual; function GetLastResults: TDBQueryList; virtual; function GetCreateCode(Obj: TDBObject): String; virtual; @@ -551,8 +562,6 @@ TDBConnection = class(TComponent) function GetSessionVariables(Refresh: Boolean): TDBQuery; function GetSessionVariable(VarName: String; DefaultValue: String=''; Refresh: Boolean=False): String; function MaxAllowedPacket: Int64; virtual; - function GetSQLSpecifity(Specifity: TSQLSpecifityId): String; overload; - function GetSQLSpecifity(Specifity: TSQLSpecifityId; const Args: array of const): String; overload; function GetDateTimeValue(Input: String; Datatype: TDBDatatypeIndex): String; procedure ClearDbObjects(db: String); procedure ClearAllDbObjects; @@ -565,6 +574,7 @@ TDBConnection = class(TComponent) function ApplyLimitClause(QueryType, QueryBody: String; Limit, Offset: Int64): String; function LikeClauseTail: String; property Parameters: TConnectionParameters read FParameters write FParameters; + property OwnsParameters: Boolean read FOwnsParameters write FOwnsParameters; property ThreadId: Int64 read GetThreadId; property ConnectionUptime: Integer read GetConnectionUptime; property ServerUptime: Integer read GetServerUptime; @@ -578,6 +588,7 @@ TDBConnection = class(TComponent) property KeyCache: TKeyCache read FKeyCache; property ForeignKeyCache: TForeignKeyCache read FForeignKeyCache; property CheckConstraintCache: TCheckConstraintCache read FCheckConstraintCache; + property StringQuoteChar: Char read FStringQuoteChar; property QuoteChar: Char read FQuoteChar; property QuoteChars: String read FQuoteChars; function ServerVersionStr: String; @@ -586,6 +597,7 @@ TDBConnection = class(TComponent) property RowsFound: Int64 read FRowsFound; property RowsAffected: Int64 read FRowsAffected; property WarningCount: Cardinal read FWarningCount; + procedure ShowWarnings; virtual; property LastQueryDuration: Cardinal read FLastQueryDuration; property LastQueryNetworkDuration: Cardinal read FLastQueryNetworkDuration; property IsUnicode: Boolean read FIsUnicode; @@ -601,7 +613,8 @@ TDBConnection = class(TComponent) function ResultCount: Integer; property CurrentUserHostCombination: String read GetCurrentUserHostCombination; property AllUserHostCombinations: TStringList read GetAllUserHostCombinations; - property LockedByThread: TThread read FLockedByThread write SetLockedByThread; + function IsLockedByThread: Boolean; + procedure SetLockedByThread(Value: TThread); virtual; property Datatypes: TDBDataTypeArray read FDatatypes; property Favorites: TStringList read FFavorites; property InfSch: String read FInfSch; @@ -615,6 +628,9 @@ TDBConnection = class(TComponent) property SQLFunctions: TSQLFunctionList read FSQLFunctions; function IsNumeric(Text: String): Boolean; function IsHex(Text: String): Boolean; + function Has(Item: TFeatureOrRequirement): Boolean; + property SqlProvider: TSqlProvider read FSqlProvider; + property NamedEnums: TStringList read FNamedEnums; published property Active: Boolean read FActive write SetActive default False; property Database: String read FDatabase write SetDatabase; @@ -637,6 +653,7 @@ TMySQLConnection = class(TDBConnection) FLastRawResults: TMySQLRawResults; FStatementNum: Cardinal; procedure SetActive(Value: Boolean); override; + procedure SetOption(Option: Integer; Arg: Pointer); procedure DoBeforeConnect; override; procedure DoAfterConnect; override; function GetThreadId: Int64; override; @@ -646,12 +663,8 @@ TMySQLConnection = class(TDBConnection) function GetLastErrorMsg: String; override; function GetAllDatabases: TStringList; override; function GetTableEngines: TStringList; override; - function GetCollationTable: TDBQuery; override; - function GetCharsetTable: TDBQuery; override; function GetCreateViewCode(Database, Name: String): String; - function GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; override; procedure FetchDbObjects(db: String; var Cache: TDBObjectList); override; - procedure SetLockedByThread(Value: TThread); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; @@ -664,6 +677,8 @@ TMySQLConnection = class(TDBConnection) function MaxAllowedPacket: Int64; override; function GetTableColumns(Table: TDBObject): TTableColumnList; override; function GetTableKeys(Table: TDBObject): TTableKeyList; override; + procedure ShowWarnings; override; + procedure SetLockedByThread(Value: TThread); override; end; TAdoRawResults = Array of _RecordSet; @@ -678,9 +693,6 @@ TAdoDBConnection = class(TDBConnection) function GetLastErrorCode: Cardinal; override; function GetLastErrorMsg: String; override; function GetAllDatabases: TStringList; override; - function GetCollationTable: TDBQuery; override; - function GetCharsetTable: TDBQuery; override; - function GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; override; procedure FetchDbObjects(db: String; var Cache: TDBObjectList); override; public constructor Create(AOwner: TComponent); override; @@ -710,7 +722,6 @@ TPgConnection = class(TDBConnection) function GetLastErrorCode: Cardinal; override; function GetLastErrorMsg: String; override; function GetAllDatabases: TStringList; override; - function GetCharsetTable: TDBQuery; override; procedure FetchDbObjects(db: String; var Cache: TDBObjectList); override; procedure Drop(Obj: TDBObject); override; public @@ -719,11 +730,10 @@ TPgConnection = class(TDBConnection) property Lib: TPostgreSQLLib read FLib; procedure Query(SQL: String; DoStoreResult: Boolean=False; LogCategory: TDBLogCategory=lcSQL); override; function Ping(Reconnect: Boolean): Boolean; override; + function GetCreateCode(Obj: TDBObject): String; override; function ConnectionInfo: TStringList; override; - function GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; override; property LastRawResults: TPGRawResults read FLastRawResults; property RegClasses: TOidStringPairs read FRegClasses; - function GetTableColumns(Table: TDBObject): TTableColumnList; override; function GetTableKeys(Table: TDBObject): TTableKeyList; override; function GetTableForeignKeys(Table: TDBObject): TForeignKeyList; override; end; @@ -750,8 +760,6 @@ TSQLiteConnection = class(TDBConnection) function GetLastErrorCode: Cardinal; override; function GetLastErrorMsg: String; override; function GetAllDatabases: TStringList; override; - function GetCollationList: TStringList; override; - function GetCharsetTable: TDBQuery; override; procedure FetchDbObjects(db: String; var Cache: TDBObjectList); override; public constructor Create(AOwner: TComponent); override; @@ -760,7 +768,6 @@ TSQLiteConnection = class(TDBConnection) procedure Query(SQL: String; DoStoreResult: Boolean=False; LogCategory: TDBLogCategory=lcSQL); override; function Ping(Reconnect: Boolean): Boolean; override; function GetCreateCode(Obj: TDBObject): String; override; - function GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; override; property LastRawResults: TSQLiteRawResults read FLastRawResults; function GetTableColumns(Table: TDBObject): TTableColumnList; override; function GetTableKeys(Table: TDBObject): TTableKeyList; override; @@ -785,8 +792,6 @@ TInterbaseConnection = class(TDBConnection) function GetLastErrorCode: Cardinal; override; function GetLastErrorMsg: String; override; function GetAllDatabases: TStringList; override; - function GetCollationTable: TDBQuery; override; - function GetCharsetTable: TDBQuery; override; procedure FetchDbObjects(db: String; var Cache: TDBObjectList); override; public constructor Create(AOwner: TComponent); override; @@ -794,7 +799,6 @@ TInterbaseConnection = class(TDBConnection) procedure Query(SQL: String; DoStoreResult: Boolean=False; LogCategory: TDBLogCategory=lcSQL); override; function Ping(Reconnect: Boolean): Boolean; override; function GetCreateCode(Obj: TDBObject): String; override; - function GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; override; property LastRawResults: TInterbaseRawResults read FLastRawResults; function GetTableColumns(Table: TDBObject): TTableColumnList; override; function GetTableKeys(Table: TDBObject): TTableKeyList; override; @@ -827,16 +831,18 @@ TDBQuery = class(TComponent) FDBObject: TDBObject; FFormatSettings: TFormatSettings; procedure SetRecNo(Value: Int64); virtual; abstract; - function ColumnExists(Column: Integer): Boolean; + function ColumnExists(Column: Integer): Boolean; overload; + function ColumnExists(ColumnName: String): Boolean; overload; procedure SetColumnOrgNames(Value: TStringList); procedure SetDBObject(Value: TDBObject); procedure CreateUpdateRow; - function GetKeyColumns: TStringList; + function GetKeyColumns: TTableColumnList; function GridQuery(QueryType, QueryBody: String): String; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Execute(AddResult: Boolean=False; UseRawResult: Integer=-1); virtual; abstract; + procedure LogMetaInfo(NumResult: Integer); procedure First; procedure Next; function ColumnCount: Integer; @@ -844,9 +850,7 @@ TDBQuery = class(TComponent) function Col(Column: Integer; IgnoreErrors: Boolean=False): String; overload; virtual; abstract; function Col(ColumnName: String; IgnoreErrors: Boolean=False): String; overload; function ColumnLengths(Column: Integer): Int64; virtual; - function HexValue(Column: Integer; IgnoreErrors: Boolean=False): String; overload; - function HexValue(BinValue: String): String; overload; - function HexValue(var ByteData: TBytes): String; overload; + function HexValue(Column: Integer; IgnoreErrors: Boolean=False): String; function DataType(Column: Integer): TDBDataType; function MaxLength(Column: Integer): Int64; function ValueList(Column: Integer): TStringList; @@ -876,6 +880,7 @@ TDBQuery = class(TComponent) function DatabaseName: String; virtual; abstract; function TableName: String; overload; function TableName(Column: Integer): String; overload; virtual; abstract; + function ResultName: String; function QuotedDbAndTableName: String; procedure DiscardModifications; procedure PrepareColumnAttributes; @@ -1012,6 +1017,8 @@ function mysql_authentication_dialog_ask( exports mysql_authentication_dialog_ask; +var + WarningShownOldOleProvider: Boolean = False; {$I const.inc} @@ -1085,7 +1092,7 @@ destructor TSecureShellCmd.Destroy; procedure TSecureShellCmd.Connect; var SshCmd, SshCmdDisplay, DialogTitle: String; - OutText, ErrorText, UserInput: String; + OutText, ErrorText, AllPipesText, UserInput: String; rx: TRegExpr; StartupInfo: TStartupInfo; ExitCode: LongWord; @@ -1093,6 +1100,7 @@ procedure TSecureShellCmd.Connect; CheckIntervalMs: Integer; IsPlink: Boolean; TimeStartedMs, WaitedMs, WaitedLeftMs, TimeOutMs: Int64; + PlinkVerMajor, PlinkVerMinor, PlinkVerRelease, PlinkVerRevision: Word; begin // Check if local port is open PortChecks := 0; @@ -1108,8 +1116,12 @@ procedure TSecureShellCmd.Connect; // plink bob@domain.com -pw myPassw0rd1 -P 22 -i "keyfile.pem" -L 55555:localhost:3306 IsPlink := ExecRegExprI('([pk]link|putty)', FConnection.Parameters.SSHExe); SshCmd := FConnection.Parameters.SSHExe; - if IsPlink then + if IsPlink then begin SshCmd := SshCmd + ' -ssh'; + GetExecutableVersion(FConnection.Parameters.SSHExe, PlinkVerMajor, PlinkVerMinor, PlinkVerRelease, PlinkVerRevision); + if (PlinkVerMajor = 0) and (PlinkVerMinor >= 82) then + SshCmd := SshCmd + ' -legacy-stdio-prompts'; + end; SshCmd := SshCmd + ' '; if FConnection.Parameters.SSHUser.Trim <> '' then SshCmd := SshCmd + FConnection.Parameters.SSHUser.Trim + '@'; @@ -1125,6 +1137,8 @@ procedure TSecureShellCmd.Connect; SshCmd := SshCmd + IfThen(IsPlink, ' -P ', ' -p ') + IntToStr(FConnection.Parameters.SSHPort); if FConnection.Parameters.SSHPrivateKey <> '' then SshCmd := SshCmd + ' -i "' + FConnection.Parameters.SSHPrivateKey + '"'; + if not IsPlink then + SshCmd := SshCmd + ' -o StrictHostKeyChecking=no'; SshCmd := SshCmd + ' -N -L ' + IntToStr(FConnection.Parameters.SSHLocalPort) + ':' + FConnection.Parameters.Hostname + ':' + IntToStr(FConnection.Parameters.Port); rx := TRegExpr.Create; rx.Expression := '(-pw\s+")[^"]*(")'; @@ -1189,21 +1203,27 @@ procedure TSecureShellCmd.Connect; end; if OutText <> '' then begin + // Prepend error text in the dialog, e.g. "Unable to use keyfile" + AllPipesText := OutText; + if not ErrorText.IsEmpty then begin + FConnection.Log(lcError, 'SSH: '+ErrorText); + AllPipesText := Trim('Error: ' + ErrorText + sLineBreak + AllPipesText); + end; if ExecRegExpr('login as\s*\:', OutText) then begin // Prompt for username - UserInput := InputBox(DialogTitle, OutText, ''); + UserInput := InputBox(DialogTitle, AllPipesText, ''); SendText(UserInput + CRLF); end else if ExecRegExpr('(password|Passphrase for key "[^"]+")\s*\:', OutText) then begin // Prompt for sensitive input. Send * as first char of prompt param so InputBox hides input characters - UserInput := InputBox(DialogTitle, #31+OutText, ''); + UserInput := InputBox(DialogTitle, #31+AllPipesText, ''); SendText(UserInput + CRLF); end else begin // Informational message box rx.Expression := '^[^\.]+\.'; if rx.Exec(OutText) then begin // First words end with a dot - use it as caption - MessageDialog(DialogTitle + ': ' + rx.Match[0], OutText, mtInformation, [mbOK]) + MessageDialog(DialogTitle + ': ' + rx.Match[0], AllPipesText, mtInformation, [mbOK]) end else begin - MessageDialog(DialogTitle, OutText, mtInformation, [mbOK]); + MessageDialog(DialogTitle, AllPipesText, mtInformation, [mbOK]); end; end; end @@ -1214,9 +1234,9 @@ procedure TSecureShellCmd.Connect; // Prompt user with question case MessageDialog(Trim(rx.Match[1]), Copy(ErrorText, 1, Length(ErrorText)-rx.MatchLen[2]), mtConfirmation, [mbYes, mbNo, mbCancel]) of mrYes: - SendText('y'); + SendText('y'+CRLF); mrNo: - SendText('n'); + SendText('n'+CRLF); mrCancel: begin Destroy; raise EDbError.Create(_('SSH command cancelled')); @@ -1378,12 +1398,15 @@ constructor TConnectionParameters.Create; begin inherited Create; FIsFolder := False; + FDeleteAfterUse := False; + FLoadedFromSettings := False; FNetType := TNetType(AppSettings.GetDefaultInt(asNetType)); FHostname := DefaultHost; FLoginPrompt := AppSettings.GetDefaultBool(asLoginPrompt); FWindowsAuth := AppSettings.GetDefaultBool(asWindowsAuth); FCleartextPluginEnabled := AppSettings.GetDefaultBool(asCleartextPluginEnabled); + FForceUnicode := AppSettings.GetDefaultBool(asForceUnicode); FUsername := DefaultUsername; FPassword := AppSettings.GetDefaultString(asPassword); FPort := DefaultPort; @@ -1407,6 +1430,7 @@ constructor TConnectionParameters.Create; FSSLCertificate := AppSettings.GetDefaultString(asSSLCert); FSSLCACertificate := AppSettings.GetDefaultString(asSSLCA); FSSLCipher := AppSettings.GetDefaultString(asSSLCipher); + FSSLVerification := AppSettings.GetDefaultInt(asSSLVerification); FStartupScriptFilename := AppSettings.GetDefaultString(asStartupScriptFilename); FQueryTimeout := AppSettings.GetDefaultInt(asQueryTimeout); FKeepAlive := AppSettings.GetDefaultInt(asKeepAlive); @@ -1450,12 +1474,14 @@ constructor TConnectionParameters.Create(SessionRegPath: String); ); FNetType := ntMySQL_TCPIP; end; + FLoadedFromSettings := True; FHostname := AppSettings.ReadString(asHost); FUsername := AppSettings.ReadString(asUser); FPassword := decrypt(AppSettings.ReadString(asPassword)); FLoginPrompt := AppSettings.ReadBool(asLoginPrompt); FWindowsAuth := AppSettings.ReadBool(asWindowsAuth); FCleartextPluginEnabled := AppSettings.ReadBool(asCleartextPluginEnabled); + FForceUnicode := AppSettings.ReadBool(asForceUnicode); FPort := MakeInt(AppSettings.ReadString(asPort)); FCompressed := AppSettings.ReadBool(asCompressed); FAllDatabases := AppSettings.ReadString(asDatabases); @@ -1481,6 +1507,7 @@ constructor TConnectionParameters.Create(SessionRegPath: String); FSSLCertificate := AppSettings.ReadString(asSSLCert); FSSLCACertificate := AppSettings.ReadString(asSSLCA); FSSLCipher := AppSettings.ReadString(asSSLCipher); + FSSLVerification := AppSettings.ReadInt(asSSLVerification); FStartupScriptFilename := AppSettings.ReadString(asStartupScriptFilename); FQueryTimeout := AppSettings.ReadInt(asQueryTimeout); FKeepAlive := AppSettings.ReadInt(asKeepAlive); @@ -1505,6 +1532,16 @@ constructor TConnectionParameters.Create(SessionRegPath: String); end; end; +destructor TConnectionParameters.Destroy; +begin + if FDeleteAfterUse and (not FLoadedFromSettings) and (not FSessionPath.IsEmpty) then begin + if AppSettings.SessionPathExists(FSessionPath) then begin + AppSettings.SessionPath := FSessionPath; + AppSettings.DeleteCurrentKey; + end; + end; +end; + procedure TConnectionParameters.SaveToRegistry; var @@ -1521,6 +1558,7 @@ procedure TConnectionParameters.SaveToRegistry; AppSettings.WriteString(asHost, FHostname); AppSettings.WriteBool(asWindowsAuth, FWindowsAuth); AppSettings.WriteBool(asCleartextPluginEnabled, FCleartextPluginEnabled); + AppSettings.WriteBool(asForceUnicode, FForceUnicode); AppSettings.WriteString(asUser, FUsername); AppSettings.WriteString(asPassword, encrypt(FPassword)); AppSettings.WriteBool(asLoginPrompt, FLoginPrompt); @@ -1550,6 +1588,7 @@ procedure TConnectionParameters.SaveToRegistry; AppSettings.WriteString(asSSLCert, FSSLCertificate); AppSettings.WriteString(asSSLCA, FSSLCACertificate); AppSettings.WriteString(asSSLCipher, FSSLCipher); + AppSettings.WriteInt(asSSLVerification, FSSLVerification); AppSettings.WriteString(asIgnoreDatabasePattern, FIgnoreDatabasePattern); AppSettings.WriteBool(asLogFileDdl, FLogFileDdl); AppSettings.WriteBool(asLogFileDml, FLogFileDml); @@ -1627,6 +1666,7 @@ function TConnectionParameters.NetTypeName(LongFormat: Boolean): String; ntPgSQL_TCPIP: Result := PrefixPostgres+' (TCP/IP)'; ntPgSQL_SSHtunnel: Result := PrefixPostgres+' (SSH tunnel)'; ntSQLite: Result := PrefixSqlite; + ntSQLiteEncrypted: Result := PrefixSqlite+' (Encrypted)'; ntInterbase_TCPIP: Result := PrefixInterbase+' (TCP/IP, experimental)'; ntInterbase_Local: Result := PrefixInterbase+' (Local, experimental)'; ntFirebird_TCPIP: Result := PrefixFirebird+' (TCP/IP, experimental)'; @@ -1667,7 +1707,7 @@ function TConnectionParameters.GetNetTypeGroup: TNetTypeGroup; Result := ngMSSQL; ntPgSQL_TCPIP, ntPgSQL_SSHtunnel: Result := ngPgSQL; - ntSQLite: + ntSQLite, ntSQLiteEncrypted: Result := ngSQLite; ntInterbase_TCPIP, ntInterbase_Local, ntFirebird_TCPIP, ntFirebird_Local: Result := ngInterbase; @@ -1724,9 +1764,15 @@ function TConnectionParameters.IsMariaDB: Boolean; function TConnectionParameters.IsMySQL(StrictDetect: Boolean): Boolean; +var + MajorVersionNum: String; begin if StrictDetect then begin - Result := IsAnyMySQL and (ContainsText(ServerVersion, 'mysql') or IsMySQLonRDS); + MajorVersionNum := RegExprGetMatch('\b(\d+)\.\d+\.\d+', ServerVersion, 1); + Result := IsAnyMySQL and (not IsMariaDB) and ( + (ContainsText(ServerVersion, 'mysql') or IsMySQLonRDS) // RDS is always MySQL, but does not contain "mysql" + or (StrToIntDef(MajorVersionNum, -1) in [3,4,5,8]) // MySQL 8.0 does not contain "mysql", but major version only exists in MySQL + ); end else begin Result := IsAnyMySQL and (not IsMariaDB) @@ -1876,7 +1922,12 @@ function TConnectionParameters.DefaultLibrary: String; ngMySQL: Result := 'libmariadb.dll'; ngMSSQL: Result := 'MSOLEDBSQL'; // Prefer MSOLEDBSQL provider on newer systems ngPgSQL: Result := 'libpq.dll'; - ngSQLite: Result := 'sqlite3.dll'; + ngSQLite: begin + if NetType = ntSQLite then + Result := 'sqlite3.dll' + else + Result := 'sqlite3mc.dll'; + end; ngInterbase: begin if IsInterbase then Result := IfThen(GetExecutableBits=64, 'ibclient64.dll', 'gds32.dll') @@ -1971,10 +2022,10 @@ function TConnectionParameters.GetLibraries: TStringList; Provider: String; begin if not Assigned(FLibraries) then begin - FLibraries := TNetGroupLibs.Create; + FLibraries := TNetTypeLibs.Create; end; - if not FLibraries.ContainsKey(NetTypeGroup) then begin + if not FLibraries.ContainsKey(NetType) then begin FoundLibs := TStringList.Create; rx := TRegExpr.Create; rx.ModifierI := True; @@ -1982,17 +2033,21 @@ function TConnectionParameters.GetLibraries: TStringList; ngMySQL: rx.Expression := '^lib(mysql|mariadb).*\.dll$'; ngMSSQL: // Allow unsupported ADODB providers per registry hack - rx.Expression := IfThen(AppSettings.ReadBool(asAllProviders), '^', '^(MSOLEDBSQL|SQLOLEDB)$'); + rx.Expression := IfThen(AppSettings.ReadBool(asAllProviders), '^', '^(MSOLEDBSQL|SQLOLEDB)'); ngPgSQL: rx.Expression := '^libpq.*\.dll$'; - ngSQLite: - rx.Expression := '^sqlite.*\.dll$'; + ngSQLite: begin + if NetType = ntSQLite then + rx.Expression := '^sqlite.*\.dll$' + else + rx.Expression := '^sqlite3mc.*\.dll$'; + end; ngInterbase: rx.Expression := '^(gds32|ibclient|fbclient).*\.dll$'; end; case NetTypeGroup of ngMySQL, ngPgSQL, ngSQLite, ngInterbase: begin - Dlls := TDirectory.GetFiles(ExtractFilePath(ParamStr(0)), '*.dll'); + Dlls := TDirectory.GetFiles(GetAppDir, '*.dll'); for DllPath in Dlls do begin DllFile := ExtractFileName(DllPath); if rx.Exec(DllFile) then begin @@ -2018,9 +2073,9 @@ function TConnectionParameters.GetLibraries: TStringList; end; end; rx.Free; - FLibraries.Add(NetTypeGroup, FoundLibs); + FLibraries.Add(NetType, FoundLibs); end; - FLibraries.TryGetValue(NetTypeGroup, Result); + FLibraries.TryGetValue(NetType, Result); end; @@ -2036,6 +2091,28 @@ function TConnectionParameters.GetSessionName: String; end; +function TConnectionParameters.GetAllDatabasesList: TStringList; +var + rx: TRegExpr; + dbname: String; +begin + Result := TStringList.Create; + if FAllDatabases <> '' then begin + rx := TRegExpr.Create; + rx.Expression := '[^;]+'; + rx.ModifierG := True; + if rx.Exec(FAllDatabases) then while true do begin + // Add if not a duplicate + dbname := Trim(rx.Match[0]); + if Result.IndexOf(dbname) = -1 then + Result.Add(dbname); + if not rx.ExecNext then + break; + end; + rx.Free; + end; +end; + { TMySQLConnection } @@ -2044,6 +2121,7 @@ constructor TDBConnection.Create(AOwner: TComponent); begin inherited; FParameters := TConnectionParameters.Create; + FOwnsParameters := True; FRowsFound := 0; FRowsAffected := 0; FWarningCount := 0; @@ -2061,7 +2139,6 @@ constructor TDBConnection.Create(AOwner: TComponent); FKeyCache := TKeyCache.Create; FForeignKeyCache := TForeignKeyCache.Create; FCheckConstraintCache := TCheckConstraintCache.Create; - FLoginPromptDone := False; FCurrentUserHostCombination := ''; FKeepAliveTimer := TTimer.Create(Self); FFavorites := TStringList.Create; @@ -2075,6 +2152,11 @@ constructor TDBConnection.Create(AOwner: TComponent); FMaxRowsPerInsert := 10000; FCaseSensitivity := 0; FStringQuoteChar := ''''; + FCollationTable := nil; + FCharsetTable := nil; + FQuoteChar := '"'; + FQuoteChars := '"[]'; + FNamedEnums := TStringList.Create; end; @@ -2099,8 +2181,6 @@ constructor TAdoDBConnection.Create(AOwner: TComponent); i: Integer; begin inherited; - FQuoteChar := '"'; - FQuoteChars := '"[]'; SetLength(FDatatypes, Length(MSSQLDatatypes)); for i:=0 to High(MSSQLDatatypes) do FDatatypes[i] := MSSQLDatatypes[i]; @@ -2114,7 +2194,6 @@ constructor TPgConnection.Create(AOwner: TComponent); i: Integer; begin inherited; - FQuoteChar := '"'; FQuoteChars := '"'; SetLength(FDatatypes, Length(PostGreSQLDatatypes)); for i:=0 to High(PostGreSQLDatatypes) do @@ -2131,8 +2210,6 @@ constructor TSQLiteConnection.Create(AOwner: TComponent); i: Integer; begin inherited; - FQuoteChar := '"'; - FQuoteChars := '"[]'; SetLength(FDatatypes, Length(SQLiteDatatypes)); for i:=0 to High(SQLiteDatatypes) do FDatatypes[i] := SQLiteDatatypes[i]; @@ -2146,8 +2223,6 @@ constructor TInterbaseConnection.Create(AOwner: TComponent); i: Integer; begin inherited; - FQuoteChar := '"'; - FQuoteChars := '"[]'; SetLength(FDatatypes, Length(InterbaseDatatypes)); for i:=0 to High(InterbaseDatatypes) do FDatatypes[i] := InterbaseDatatypes[i]; @@ -2162,6 +2237,9 @@ destructor TDBConnection.Destroy; FKeepAliveTimer.Free; FFavorites.Free; FInformationSchemaObjects.Free; + FNamedEnums.Free; + if FOwnsParameters then + FreeAndNil(FParameters); inherited; end; @@ -2176,7 +2254,14 @@ destructor TMySQLConnection.Destroy; destructor TAdoDBConnection.Destroy; begin if Active then Active := False; - FreeAndNil(FAdoHandle); + try + FreeAndNil(FAdoHandle); + except + on E:Exception do begin + // Destroy > ClearRefs > GetDataSetCount throws some error, but max in Delphi 11.2 yet + Log(lcError, E.Message); + end; + end; inherited; end; @@ -2217,6 +2302,8 @@ function TDBConnection.GetDatatypeByName(var DataType: String; DeleteFromSource: rx := TRegExpr.Create; rx.ModifierI := True; MatchLen := 0; + // Remove quotes around PG enums: "UserStatus" + DataType := DeQuoteIdent(DataType); for i:=0 to High(FDatatypes) do begin Types := FDatatypes[i].Name; if FDatatypes[i].Names <> '' then begin @@ -2272,8 +2359,13 @@ function TDBConnection.GetDatatypeByNativeType(NativeType: Integer; Identifier: begin rx := TRegExpr.Create; TypeFound := False; + for i:=0 to High(Datatypes) do begin - if Datatypes[i].NativeTypes = '' then + // Skip if native ids / oid's are (yet) empty + if Datatypes[i].NativeTypes.IsEmpty then + Continue; + // Skip ? and e which have a special meaning + if Datatypes[i].NativeTypes.Length = 1 then Continue; rx.Expression := '\b('+Datatypes[i].NativeTypes+')\b'; if rx.Exec(IntToStr(NativeType)) then begin @@ -2282,6 +2374,16 @@ function TDBConnection.GetDatatypeByNativeType(NativeType: Integer; Identifier: break; end; end; + + { Dynamically retrieve data type from pg_type. + Problematic because we would not know which TDBDatatypeIndex to assign. + if (not TypeFound) and Parameters.IsAnyPostgreSQL then begin + PgType := GetResults('SELECT * FROM '+QuoteIdent('pg_type')+' WHERE '+QuoteIdent('oid')+'='+NativeType.ToString); + if PgType.RecordCount = 1 then begin + SetLength(FDatatypes, Length(FDatatypes)+1); + end; + end;} + if not TypeFound then begin // Fall back to unknown type Result := Datatypes[0]; @@ -2315,6 +2417,11 @@ procedure TMySQLConnection.SetLockedByThread(Value: TThread); end; end; +function TDBConnection.IsLockedByThread: Boolean; +begin + Result := FLockedByThread <> nil; +end; + {** (Dis-)Connect to/from server @@ -2322,15 +2429,14 @@ procedure TMySQLConnection.SetLockedByThread(Value: TThread); procedure TMySQLConnection.SetActive( Value: Boolean ); var Connected: PMYSQL; - ClientFlags, FinalPort: Integer; + ClientFlags, FinalPort, SSLoption: Integer; + VerifyServerCert: Integer; Error, StatusName: String; FinalHost, FinalSocket, FinalUsername, FinalPassword: String; ErrorHint: String; - sslca, sslkey, sslcert, sslcipher: PAnsiChar; - PluginDir: AnsiString; + PluginDir, TlsVersions: AnsiString; Status: TDBQuery; PasswordChangeDialog: TfrmPasswordChange; - SetOptionResult: Integer; UserNameSize: DWORD; begin if Value and (FHandle = nil) then begin @@ -2346,27 +2452,48 @@ procedure TMySQLConnection.SetActive( Value: Boolean ); FinalPort := FParameters.Port; if FParameters.WantSSL then begin - // mysql_ssl_set() wants nil, while PAnsiChar(AnsiString()) is never nil - sslkey := nil; - sslcert := nil; - sslca := nil; - sslcipher := nil; + // Define which TLS protocol versions are allowed. + // See https://www.heidisql.com/forum.php?t=27158 + // See https://mariadb.com/kb/en/library/mysql_optionsv/ + // See issue #1768 + TlsVersions := 'TLSv1,TLSv1.1,TLSv1.2,TLSv1.3'; + //TlsVersions := 'TLSv1.1'; + if FLib.MARIADB_OPT_TLS_VERSION <> FLib.INVALID_OPT then + SetOption(FLib.MARIADB_OPT_TLS_VERSION, PAnsiChar(TlsVersions)); + SetOption(FLib.MYSQL_OPT_TLS_VERSION, PAnsiChar(TlsVersions)); if FParameters.SSLPrivateKey <> '' then - sslkey := PAnsiChar(AnsiString(FParameters.SSLPrivateKey)); + SetOption(FLib.MYSQL_OPT_SSL_KEY, PAnsiChar(AnsiString(FParameters.SSLPrivateKey))); if FParameters.SSLCertificate <> '' then - sslcert := PAnsiChar(AnsiString(FParameters.SSLCertificate)); + SetOption(FLib.MYSQL_OPT_SSL_CERT, PAnsiChar(AnsiString(FParameters.SSLCertificate))); if FParameters.SSLCACertificate <> '' then - sslca := PAnsiChar(AnsiString(FParameters.SSLCACertificate)); + SetOption(FLib.MYSQL_OPT_SSL_CA, PAnsiChar(AnsiString(FParameters.SSLCACertificate))); if FParameters.SSLCipher <> '' then - sslcipher := PAnsiChar(AnsiString(FParameters.SSLCipher)); - { TODO : Use Cipher and CAPath parameters } - FLib.mysql_ssl_set(FHandle, - sslkey, - sslcert, - sslca, - nil, - sslcipher); - Log(lcInfo, _('SSL parameters successfully set.')); + SetOption(FLib.MYSQL_OPT_SSL_CIPHER, PAnsiChar(AnsiString(FParameters.SSLCipher))); + if not FLib.IsLibMariadb then begin + // MySQL + Log(lcInfo, 'SSL parameters for MySQL'); + case FParameters.SSLVerification of + 0: SSLoption := FLib.SSL_MODE_PREFERRED; + 1: SSLoption := FLib.SSL_MODE_VERIFY_CA; + 2: SSLoption := FLib.SSL_MODE_VERIFY_IDENTITY; + end; + SetOption(FLib.MYSQL_OPT_SSL_MODE, @SSLoption); + end + else begin + // MariaDB + Log(lcInfo, 'SSL parameters for MariaDB'); + case FParameters.SSLVerification of + 0: VerifyServerCert := FLib.MYBOOL_FALSE; + 1,2: VerifyServerCert := FLib.MYBOOL_TRUE; + end; + SetOption(FLib.MYSQL_OPT_SSL_VERIFY_SERVER_CERT, @VerifyServerCert); + end; + end; + + // libmariadb v3.4.0+ enables MYSQL_OPT_SSL_VERIFY_SERVER_CERT by default, so we have to disable it. + // See https://mariadb.com/kb/en/mariadb-connector-c-3-4-0-release-notes/ + if not FParameters.WantSSL then begin + SetOption(FLib.MYSQL_OPT_SSL_VERIFY_SERVER_CERT, @(FLib.MYBOOL_FALSE)); end; case FParameters.NetType of @@ -2408,35 +2535,26 @@ procedure TMySQLConnection.SetActive( Value: Boolean ); or CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA; if Parameters.Compressed then ClientFlags := ClientFlags or CLIENT_COMPRESS; - if Parameters.WantSSL then + if Parameters.WantSSL and (not FLib.IsLibMariadb) then ClientFlags := ClientFlags or CLIENT_SSL; // Point libmysql to the folder with client plugins - PluginDir := AnsiString(ExtractFilePath(ParamStr(0))+'plugins'); - SetOptionResult := FLib.mysql_options(FHandle, Integer(MYSQL_PLUGIN_DIR), PAnsiChar(PluginDir)); - if SetOptionResult <> 0 then begin - raise EDbError.Create(f_('Plugin directory %s could not be set.', [PluginDir])); - end; - - // Define which TLS protocol versions are allowed. - // See https://www.heidisql.com/forum.php?t=27158 - // See https://mariadb.com/kb/en/library/mysql_optionsv/ - FLib.mysql_options(FHandle, Integer(MARIADB_OPT_TLS_VERSION), PAnsiChar('TLSv1,TLSv1.1,TLSv1.2,TLSv1.3')); - FLib.mysql_options(FHandle, Integer(MYSQL_OPT_TLS_VERSION), PAnsiChar('TLSv1,TLSv1.1,TLSv1.2,TLSv1.3')); + PluginDir := AnsiString(GetAppDir+'plugins'); + SetOption(FLib.MYSQL_PLUGIN_DIR, PAnsiChar(PluginDir)); // Enable cleartext plugin if Parameters.CleartextPluginEnabled then - FLib.mysql_options(FHandle, Integer(MYSQL_ENABLE_CLEARTEXT_PLUGIN), PAnsiChar('1')); + SetOption(FLib.MYSQL_ENABLE_CLEARTEXT_PLUGIN, @(FLib.MYBOOL_TRUE)); // Tell server who we are if Assigned(FLib.mysql_optionsv) then - FLib.mysql_optionsv(FHandle, Integer(MYSQL_OPT_CONNECT_ATTR_ADD), 'program_name', APPNAME); + FLib.mysql_optionsv(FHandle, FLib.MYSQL_OPT_CONNECT_ATTR_ADD, 'program_name', APPNAME); // Seems to be still required on some systems, for importing CSV files - FLib.mysql_options(FHandle, Integer(MYSQL_OPT_LOCAL_INFILE), PAnsiChar('1')); + SetOption(FLib.MYSQL_OPT_LOCAL_INFILE, @(FLib.MYBOOL_TRUE)); // Ensure we have some connection timeout - FLib.mysql_options(FHandle, Integer(MYSQL_OPT_CONNECT_TIMEOUT), @FParameters.QueryTimeout); + SetOption(FLib.MYSQL_OPT_CONNECT_TIMEOUT, @(FParameters.QueryTimeout)); Connected := FLib.mysql_real_connect( FHandle, @@ -2454,11 +2572,20 @@ procedure TMySQLConnection.SetActive( Value: Boolean ); FConnectionStarted := 0; FHandle := nil; EndSSHTunnel; - if (FParameters.DefaultLibrary <> '') and (FParameters.LibraryOrProvider <> FParameters.DefaultLibrary) then begin + if Error.Contains('SEC_E_ALGORITHM_MISMATCH') then begin + ErrorHint := f_('This is a known issue with older libraries. Try a newer %s in the session settings.', + ['libmysql'] + ); + end + else if Error.Contains('certificate verif') then begin + ErrorHint := _('You might need to lower the certificate verification in the SSL settings.'); + end + else if (FParameters.DefaultLibrary <> '') and (FParameters.LibraryOrProvider <> FParameters.DefaultLibrary) then begin ErrorHint := f_('You could try the default library %s in your session settings. (Current: %s)', [FParameters.DefaultLibrary, FParameters.LibraryOrProvider] ); - end else begin + end + else begin ErrorHint := ''; end; raise EDbError.Create(Error, LastErrorCode, ErrorHint); @@ -2506,8 +2633,6 @@ procedure TMySQLConnection.SetActive( Value: Boolean ); end; // mysql_character_set_name() reports utf8* if in fact we're on some latin* charset on v5.1 servers // See https://www.heidisql.com/forum.php?t=39278 - FIsUnicode := CharacterSet.StartsWith('utf', True) and (ServerVersionInt >= 50500); - if not IsUnicode then try CharacterSet := 'utf8mb4'; except @@ -2530,7 +2655,7 @@ procedure TMySQLConnection.SetActive( Value: Boolean ); Log(lcInfo, _('Characterset')+': '+CharacterSet); FConnectionStarted := GetTickCount div 1000; FServerUptime := -1; - Status := GetResults(GetSQLSpecifity(spGlobalStatus)); + Status := GetResults(FSqlProvider.GetSql(qGlobalStatus)); while not Status.Eof do begin StatusName := LowerCase(Status.Col(0)); if (StatusName = 'uptime') or (StatusName = 'proxysql_uptime') then @@ -2539,7 +2664,7 @@ procedure TMySQLConnection.SetActive( Value: Boolean ); FIsSSL := Status.Col(1) <> ''; Status.Next; end; - FServerDateTimeOnStartup := GetVar('SELECT ' + GetSQLSpecifity(spFuncNow)); + FServerDateTimeOnStartup := GetVar('SELECT ' + FSqlProvider.GetSql(qFuncNow)); FServerOS := GetSessionVariable('version_compile_os'); FRealHostname := GetSessionVariable('hostname'); FCaseSensitivity := MakeInt(GetSessionVariable('lower_case_table_names', IntToStr(FCaseSensitivity))); @@ -2596,13 +2721,14 @@ procedure TAdoDBConnection.SetActive(Value: Boolean); end; IsOldProvider := Parameters.LibraryOrProvider = 'SQLOLEDB'; - if IsOldProvider then begin + if IsOldProvider and (not WarningShownOldOleProvider) then begin MessageDialog( f_('Security issue: Using %s %s with insecure %s.', [Parameters.LibraryOrProvider, 'ADO provider', 'TLS 1.0']) + f_('You should install %s from %s', ['Microsoft OLE DB Driver', 'https://www.microsoft.com/en-us/download/confirmation.aspx?id=56730']), mtWarning, [mbOK]); + WarningShownOldOleProvider := True; end; NetLib := ''; @@ -2633,7 +2759,7 @@ procedure TAdoDBConnection.SetActive(Value: Boolean); 'Data Source='+DataSource+';'+ 'Application Name='+AppName+';' ; - if Parameters.LibraryOrProvider = 'MSOLEDBSQL' then begin + if Parameters.LibraryOrProvider.StartsWith('MSOLEDBSQL', true) then begin // Issue #423: MSOLEDBSQL compatibility with new column types // See https://docs.microsoft.com/en-us/sql/connect/oledb/applications/using-ado-with-oledb-driver-for-sql-server?view=sql-server-2017 // Do not use with old driver, see https://www.heidisql.com/forum.php?t=35208 @@ -2667,7 +2793,7 @@ procedure TAdoDBConnection.SetActive(Value: Boolean); except FServerUptime := -1; end; - FServerDateTimeOnStartup := GetVar('SELECT ' + GetSQLSpecifity(spFuncNow)); + FServerDateTimeOnStartup := GetVar('SELECT ' + FSqlProvider.GetSql(qFuncNow)); // Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (Intel X86) // Apr 2 2010 15:53:02 // Copyright (c) Microsoft Corporation @@ -2739,24 +2865,13 @@ procedure TAdoDBConnection.SetActive(Value: Boolean); procedure TPgConnection.SetActive(Value: Boolean); var - dbname, ConnInfo, Error: String; + ConnectionString, OptionValue, Error: String; + ConnectOptions: TStringList; FinalHost, ErrorHint: String; - FinalPort: Integer; - - function EscapeConnectOption(Option: String): String; - begin - // See issue #704 and #1417, and docs: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING - Result := StringReplace(Option, '\', '\\', [rfReplaceAll]); - Result := StringReplace(Result, '''', '\''', [rfReplaceAll]); - end; + FinalPort, i: Integer; begin if Value then begin DoBeforeConnect; - // Simon Riggs: - // "You should connect as "postgres" database by default, with an option to change. Don't use template1" - dbname := FParameters.AllDatabasesStr; - if dbname = '' then - dbname := 'postgres'; // Prepare special stuff for SSH tunnel FinalHost := FParameters.Hostname; @@ -2764,25 +2879,45 @@ procedure TPgConnection.SetActive(Value: Boolean); StartSSHTunnel(FinalHost, FinalPort); - ConnInfo := 'host='''+EscapeConnectOption(FinalHost)+''' '+ - 'port='''+IntToStr(FinalPort)+''' '+ - 'user='''+EscapeConnectOption(FParameters.Username)+''' ' + - 'password='''+EscapeConnectOption(FParameters.Password)+''' '+ - 'dbname='''+EscapeConnectOption(dbname)+''' '+ - 'application_name='''+EscapeConnectOption(APPNAME)+''''; + // Compose connection string + ConnectOptions := TStringList.Create; + ConnectOptions.Duplicates := dupIgnore; + ConnectOptions + .AddPair('host', FinalHost) + .AddPair('port', IntToStr(FinalPort)) + .AddPair('user', FParameters.Username) + .AddPair('password', FParameters.Password) + .AddPair('application_name', APPNAME) + .AddPair('sslmode', 'disable'); + if not FParameters.AllDatabasesStr.IsEmpty then + ConnectOptions.AddPair('dbname', FParameters.AllDatabasesStr); if FParameters.WantSSL then begin - ConnInfo := ConnInfo + ' sslmode=''require'''; + // Be aware .AddPair would add duplicates + case FParameters.SSLVerification of + 0: ConnectOptions.Values['sslmode'] := 'require'; + 1: ConnectOptions.Values['sslmode'] := 'verify-ca'; + 2: ConnectOptions.Values['sslmode'] := 'verify-full'; + end; if FParameters.SSLPrivateKey <> '' then - ConnInfo := ConnInfo + ' sslkey='''+EscapeConnectOption(FParameters.SSLPrivateKey)+''''; + ConnectOptions.AddPair('sslkey', FParameters.SSLPrivateKey); if FParameters.SSLCertificate <> '' then - ConnInfo := ConnInfo + ' sslcert='''+EscapeConnectOption(FParameters.SSLCertificate)+''''; + ConnectOptions.AddPair('sslcert', FParameters.SSLCertificate); if FParameters.SSLCACertificate <> '' then - ConnInfo := ConnInfo + ' sslrootcert='''+EscapeConnectOption(FParameters.SSLCACertificate)+''''; + ConnectOptions.AddPair('sslrootcert', FParameters.SSLCACertificate); //if FParameters.SSLCipher <> '' then ?? end; + ConnectionString := ''; + for i:=0 to ConnectOptions.Count-1 do begin + // Escape values. See issue #704 and #1417, and docs: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING + OptionValue := ConnectOptions.ValueFromIndex[i]; + OptionValue := StringReplace(OptionValue, '\', '\\', [rfReplaceAll]); + OptionValue := StringReplace(OptionValue, '''', '\''', [rfReplaceAll]); + ConnectionString := ConnectionString + ConnectOptions.Names[i] + '=''' + OptionValue + ''' '; + end; + ConnectOptions.Free; + ConnectionString := ConnectionString.TrimRight; - - FHandle := FLib.PQconnectdb(PAnsiChar(AnsiString(ConnInfo))); + FHandle := FLib.PQconnectdb(PAnsiChar(UTF8Encode(ConnectionString))); if FLib.PQstatus(FHandle) = CONNECTION_BAD then begin Error := LastErrorMsg; Log(lcError, Error); @@ -2804,10 +2939,13 @@ procedure TPgConnection.SetActive(Value: Boolean); raise EDbError.Create(Error, LastErrorCode, ErrorHint); end; FActive := True; - FServerDateTimeOnStartup := GetVar('SELECT ' + GetSQLSpecifity(spFuncNow)); + CharacterSet := 'UTF8'; + FServerDateTimeOnStartup := GetVar('SELECT ' + FSqlProvider.GetSql(qFuncNow)); FServerVersionUntouched := GetVar('SELECT VERSION()'); FConnectionStarted := GetTickCount div 1000; Query('SET statement_timeout TO '+IntToStr(Parameters.QueryTimeout*1000)); + if ServerVersionInt >= 80300 then + Query('SET synchronize_seqscans TO off'); try FServerUptime := StrToIntDef(GetVar('SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP - pg_postmaster_start_time())::INTEGER'), -1); except @@ -2824,7 +2962,8 @@ procedure TPgConnection.SetActive(Value: Boolean); DoAfterConnect; end else begin try - FLib.PQfinish(FHandle); + if FActive then + FLib.PQfinish(FHandle); except on E:EAccessViolation do; end; @@ -2840,16 +2979,28 @@ procedure TPgConnection.SetActive(Value: Boolean); procedure TSQLiteConnection.SetActive(Value: Boolean); var ConnectResult: Integer; + RawPassword: AnsiString; ErrorHint: String; - FileNames: TStringList; - MainFile, DbAlias: String; - i: Integer; + FileNames, EncryptionParams: TStringList; + MainFile, DbAlias, Param, ParamName: String; + MainFileDir: String; + i, SplitPos, ParamValue: Integer; + CipherIndex, ConfigResult: Integer; + ParamWasSet: Boolean; begin // Support multiple filenames, and use first one as main database FileNames := Explode(DELIM, Parameters.Hostname); MainFile := IfThen(FileNames.Count>=1, FileNames[0], ''); if Value then begin + // Fixes "out of memory" crash in sqlite3_open, see issue #1367 + MainFileDir := ExtractFilePath(MainFile); + MainFileDir := IncludeTrailingPathDelimiter(MainFileDir); + if not DirectoryExists(MainFileDir) then + raise EDbError.Create(f_('Folder in path does not exist: %s', [MainFile])); + if not FileExists(MainFile) then + Log(lcInfo, f_('File does not yet exist, will be created now: %s', [MainFile])); + DoBeforeConnect; ConnectResult := FLib.sqlite3_open( @@ -2858,6 +3009,56 @@ procedure TSQLiteConnection.SetActive(Value: Boolean); if ConnectResult = SQLITE_OK then begin FActive := True; + if Parameters.NetType = ntSQLiteEncrypted then begin + // Use encryption key + CipherIndex := FLib.sqlite3mc_cipher_index(PAnsiChar(AnsiString(Parameters.Username))); + //Log(lcinfo, 'CipherIndex:'+CipherIndex.ToString); + if CipherIndex = -1 then + raise EDbError.Create(f_('Warning: Given cipher scheme name "%s" could not be found', [Parameters.Username])); + ConfigResult := FLib.sqlite3mc_config(FHandle, PAnsiChar('default:cipher'), CipherIndex); + if ConfigResult = -1 then + raise EDbError.Create(f_('Warning: Configuring with cipher index %d failed', [CipherIndex])); + // Set encryption parameters: + EncryptionParams := Parameters.AllDatabasesList; + for Param in EncryptionParams do begin + Log(lcDebug, 'Cipher encryption parameter: "'+Param+'"'); + SplitPos := Param.IndexOf('='); + ParamWasSet := False; + if SplitPos > -1 then begin + ParamName := Copy(Param, 1, SplitPos); + ParamValue := StrToIntDef(Copy(Param, SplitPos+2, Length(Param)), -1); + if ParamValue > -1 then begin + ConfigResult := FLib.sqlite3mc_config_cipher( + FHandle, + PAnsiChar(AnsiString(Parameters.Username)), + PAnsiChar(AnsiString(ParamName)), + ParamValue + ); + if ConfigResult <> -1 then + ParamWasSet := True; + end + end; + if not ParamWasSet then + Log(lcError, f_('Warning: Failed to set cipher encryption parameter "%s"', [Param])) + else + Log(lcInfo, f_('Info: Cipher encryption parameter "%s" set', [Param])); + end; + // Set the main database key + RawPassword := AnsiString(Parameters.Password); + FLib.sqlite3_key(FHandle, Pointer(RawPassword), Length(RawPassword)); + // See https://utelle.github.io/SQLite3MultipleCiphers/docs/configuration/config_capi/ + // "These functions return SQLITE_OK even if the provided key isnt correct. This is because the key isnt + // actually used until a subsequent attempt to read or write the database is made. To check whether the + // provided key was actually correct, you must execute a simple query like e.g. SELECT * FROM sqlite_master; + // and check whether that succeeds." + try + Query(ApplyLimitClause('SELECT', '* FROM sqlite_master', 1, 0)); + except + on E:EDbError do + raise EDbError.Create(E.Message, 0, _('You have activated encryption on a probably non-encrypted database.')); + end; + end; + FLib.sqlite3_collation_needed(FHandle, Self, SQLite_CollationNeededCallback); Query('PRAGMA busy_timeout='+(Parameters.QueryTimeout*1000).ToString); // Override "main" database name with custom one @@ -2875,7 +3076,7 @@ procedure TSQLiteConnection.SetActive(Value: Boolean); Log(lcError, 'Could not enable load_extension()'); end; - FServerDateTimeOnStartup := GetVar('SELECT ' + GetSQLSpecifity(spFuncNow)); + FServerDateTimeOnStartup := GetVar('SELECT ' + FSqlProvider.GetSql(qFuncNow)); FServerVersionUntouched := GetVar('SELECT sqlite_version()'); FConnectionStarted := GetTickCount div 1000; FServerUptime := -1; @@ -2982,7 +3183,7 @@ procedure TInterbaseConnection.SetActive(Value: Boolean); FActive := True; //! Query('PRAGMA busy_timeout='+(Parameters.QueryTimeout*1000).ToString); - FServerDateTimeOnStartup := GetVar('SELECT ' + GetSQLSpecifity(spFuncNow)); + FServerDateTimeOnStartup := GetVar('SELECT ' + FSqlProvider.GetSql(qFuncNow)); if Parameters.IsInterbase then FServerVersionUntouched := '' @@ -3011,180 +3212,91 @@ procedure TInterbaseConnection.SetActive(Value: Boolean); end; +procedure TMySQLConnection.SetOption(Option: Integer; Arg: Pointer); +var + SetOptionResult: Integer; + RttiContext: TRttiContext; + LibType: TRttiType; + LibField: TRttiField; + FieldName: String; +begin + // Set one of the MYSQL_* option and log a warning if that failed + FieldName := Option.ToString; + // Attempt to find readable name of option constant + RttiContext := TRttiContext.Create; + LibType := RttiContext.GetType(TypeInfo(TMySQLLib)); + for LibField in LibType.GetFields do begin + // Skip assigned procedures + if LibField.FieldType = nil then + Continue; + if LibField.DataType.TypeKind = tkInteger then begin + if LibField.GetValue(FLib).AsInteger = Option then begin + FieldName := LibField.Name; + end; + end; + end; + RttiContext.Free; + + Log(lcDebug, Format('Calling mysql_options(%s, ...)', [FieldName])); + SetOptionResult := FLib.mysql_options(FHandle, Option, Arg); + if SetOptionResult <> 0 then begin + Log(lcError, _(SLogPrefixWarning) + ': mysql_options(' + FieldName + ', ...) failed!'); + end; +end; + + procedure TDBConnection.DoBeforeConnect; var UsingPass: String; Dialog: TfrmLogin; begin - // Prompt for password on initial connect - if FParameters.LoginPrompt and (not FLoginPromptDone) then begin + // Don't remember prompt values + if FParameters.LoginPrompt then begin Dialog := TfrmLogin.Create(Self); Dialog.Caption := APPNAME + ' - ' + FParameters.SessionName; Dialog.lblPrompt.Caption := f_('Login to %s:', [FParameters.Hostname]); Dialog.editUsername.Text := FParameters.Username; Dialog.editPassword.Text := FParameters.Password; - Dialog.ShowModal; - FParameters.Username := Dialog.editUsername.Text; - FParameters.Password := Dialog.editPassword.Text; - Dialog.Free; - FLoginPromptDone := True; + if Dialog.ShowModal = mrOk then begin + FParameters.Username := Dialog.editUsername.Text; + FParameters.Password := Dialog.editPassword.Text; + Dialog.Free; + end + else begin + Dialog.Free; + raise EDbError.Create(_('Login cancelled')); + end; end; // Prepare connection - if FParameters.Password <> '' then UsingPass := 'Yes' else UsingPass := 'No'; - Log(lcInfo, f_('Connecting to %s via %s, username %s, using password: %s ...', - [FParameters.Hostname, FParameters.NetTypeName(True), FParameters.Username, UsingPass] - )); - - FSQLSpecifities[spOrderAsc] := 'ASC'; - FSQLSpecifities[spOrderDesc] := 'DESC'; - FSQLSpecifities[spForeignKeyEventAction] := 'RESTRICT,CASCADE,SET NULL,NO ACTION'; - - case Parameters.NetTypeGroup of - ngMySQL: begin - FSQLSpecifities[spDatabaseDrop] := 'DROP DATABASE %s'; - FSQLSpecifities[spEmptyTable] := 'TRUNCATE '; - FSQLSpecifities[spRenameTable] := 'RENAME TABLE %s TO %s'; - FSQLSpecifities[spRenameView] := FSQLSpecifities[spRenameTable]; - FSQLSpecifities[spCurrentUserHost] := 'SELECT CURRENT_USER()'; - FSQLSpecifities[spLikeCompare] := '%s LIKE %s'; - FSQLSpecifities[spAddColumn] := 'ADD COLUMN %s'; - FSQLSpecifities[spChangeColumn] := 'CHANGE COLUMN %s %s'; - FSQLSpecifities[spGlobalStatus] := IfThen( - Parameters.IsProxySQLAdmin, - 'SELECT * FROM stats_mysql_global', - 'SHOW /*!50002 GLOBAL */ STATUS' - ); - FSQLSpecifities[spCommandsCounters] := IfThen( - Parameters.IsProxySQLAdmin, - 'SELECT * FROM stats_mysql_commands_counters', - 'SHOW /*!50002 GLOBAL */ STATUS LIKE ''Com\_%''' - ); - FSQLSpecifities[spSessionVariables] := 'SHOW VARIABLES'; - FSQLSpecifities[spGlobalVariables] := 'SHOW GLOBAL VARIABLES'; - FSQLSpecifities[spISSchemaCol] := '%s_SCHEMA'; - FSQLSpecifities[spUSEQuery] := 'USE %s'; - if Parameters.NetType = ntMySQL_RDS then begin - FSQLSpecifities[spKillQuery] := 'CALL mysql.rds_kill_query(%d)'; - FSQLSpecifities[spKillProcess] := 'CALL mysql.rds_kill(%d)' - end - else begin - FSQLSpecifities[spKillQuery] := 'KILL %d'; // may be overwritten in DoAfterConnect - FSQLSpecifities[spKillProcess] := 'KILL %d'; - end; - FSQLSpecifities[spFuncLength] := 'LENGTH'; - FSQLSpecifities[spFuncCeil] := 'CEIL'; - FSQLSpecifities[spFuncLeft] := IfThen(Parameters.IsProxySQLAdmin, 'SUBSTR(%s, 1, %d)', 'LEFT(%s, %d)'); - FSQLSpecifities[spFuncNow] := IfThen(Parameters.IsProxySQLAdmin, 'CURRENT_TIMESTAMP', 'NOW()'); - FSQLSpecifities[spFuncLastAutoIncNumber] := 'LAST_INSERT_ID()'; - FSQLSpecifities[spLockedTables] := ''; - FSQLSpecifities[spDisableForeignKeyChecks] := 'SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0'; - FSQLSpecifities[spEnableForeignKeyChecks] := 'SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1)'; - end; - ngMSSQL: begin - FSQLSpecifities[spDatabaseDrop] := 'DROP DATABASE %s'; - FSQLSpecifities[spEmptyTable] := 'DELETE FROM '; - FSQLSpecifities[spRenameTable] := 'EXEC sp_rename %s, %s'; - FSQLSpecifities[spRenameView] := FSQLSpecifities[spRenameTable]; - FSQLSpecifities[spCurrentUserHost] := 'SELECT SYSTEM_USER'; - FSQLSpecifities[spLikeCompare] := '%s LIKE %s'; - FSQLSpecifities[spAddColumn] := 'ADD %s'; - FSQLSpecifities[spChangeColumn] := 'ALTER COLUMN %s %s'; - FSQLSpecifities[spSessionVariables] := 'SELECT '+QuoteIdent('comment')+', '+QuoteIdent('value')+' FROM '+QuoteIdent('master')+'.'+QuoteIdent('dbo')+'.'+QuoteIdent('syscurconfigs')+' ORDER BY '+QuoteIdent('comment'); - FSQLSpecifities[spGlobalVariables] := FSQLSpecifities[spSessionVariables]; - FSQLSpecifities[spISSchemaCol] := '%s_CATALOG'; - FSQLSpecifities[spUSEQuery] := 'USE %s'; - FSQLSpecifities[spKillQuery] := 'KILL %d'; - FSQLSpecifities[spKillProcess] := 'KILL %d'; - FSQLSpecifities[spFuncLength] := 'LEN'; - FSQLSpecifities[spFuncCeil] := 'CEILING'; - FSQLSpecifities[spFuncLeft] := 'LEFT(%s, %d)'; - FSQLSpecifities[spFuncNow] := 'GETDATE()'; - FSQLSpecifities[spFuncLastAutoIncNumber] := 'LAST_INSERT_ID()'; - FSQLSpecifities[spLockedTables] := ''; - FSQLSpecifities[spDisableForeignKeyChecks] := ''; - FSQLSpecifities[spEnableForeignKeyChecks] := ''; - end; - ngPgSQL: begin - FSQLSpecifities[spDatabaseDrop] := 'DROP SCHEMA %s'; - FSQLSpecifities[spEmptyTable] := 'DELETE FROM '; - FSQLSpecifities[spRenameTable] := 'ALTER TABLE %s RENAME TO %s'; - FSQLSpecifities[spRenameView] := 'ALTER VIEW %s RENAME TO %s'; - FSQLSpecifities[spCurrentUserHost] := 'SELECT CURRENT_USER'; - FSQLSpecifities[spLikeCompare] := '%s ILIKE %s'; - FSQLSpecifities[spAddColumn] := 'ADD %s'; - FSQLSpecifities[spChangeColumn] := 'ALTER COLUMN %s %s'; - FSQLSpecifities[spRenameColumn] := 'RENAME COLUMN %s TO %s'; - FSQLSpecifities[spForeignKeyEventAction] := 'RESTRICT,CASCADE,SET NULL,NO ACTION,SET DEFAULT'; - FSQLSpecifities[spSessionVariables] := 'SHOW ALL'; - FSQLSpecifities[spGlobalVariables] := FSQLSpecifities[spSessionVariables]; - FSQLSpecifities[spISSchemaCol] := '%s_schema'; - FSQLSpecifities[spUSEQuery] := 'SET search_path TO %s'; - FSQLSpecifities[spKillQuery] := 'SELECT pg_cancel_backend(%d)'; - FSQLSpecifities[spKillProcess] := 'SELECT pg_cancel_backend(%d)'; - FSQLSpecifities[spFuncLength] := 'LENGTH'; - FSQLSpecifities[spFuncCeil] := 'CEIL'; - FSQLSpecifities[spFuncLeft] := 'SUBSTRING(%s, 1, %d)'; - FSQLSpecifities[spFuncNow] := 'NOW()'; - FSQLSpecifities[spFuncLastAutoIncNumber] := 'LASTVAL()'; - FSQLSpecifities[spLockedTables] := ''; - FSQLSpecifities[spDisableForeignKeyChecks] := ''; - FSQLSpecifities[spEnableForeignKeyChecks] := ''; - end; + UsingPass := IfThen(FParameters.Password.IsEmpty, 'No', 'Yes'); + case FParameters.NetTypeGroup of ngSQLite: begin - FSQLSpecifities[spDatabaseDrop] := 'DROP DATABASE %s'; - FSQLSpecifities[spEmptyTable] := 'DELETE FROM '; - FSQLSpecifities[spRenameTable] := 'ALTER TABLE %s RENAME TO %s'; - FSQLSpecifities[spRenameView] := FSQLSpecifities[spRenameTable]; - FSQLSpecifities[spCurrentUserHost] := ''; // unsupported - FSQLSpecifities[spLikeCompare] := '%s LIKE %s'; - FSQLSpecifities[spAddColumn] := 'ADD COLUMN %s'; - FSQLSpecifities[spChangeColumn] := ''; // SQLite only supports renaming - FSQLSpecifities[spRenameColumn] := 'RENAME COLUMN %s TO %s'; - FSQLSpecifities[spSessionVariables] := 'SELECT null, null'; // Todo: combine "PRAGMA pragma_list" + "PRAGMA a; PRAGMY b; ..."? - FSQLSpecifities[spGlobalVariables] := 'SHOW GLOBAL VARIABLES'; - FSQLSpecifities[spISSchemaCol] := '%s_SCHEMA'; - FSQLSpecifities[spUSEQuery] := ''; - FSQLSpecifities[spKillQuery] := 'KILL %d'; - FSQLSpecifities[spKillProcess] := 'KILL %d'; - FSQLSpecifities[spFuncLength] := 'LENGTH'; - FSQLSpecifities[spFuncCeil] := 'CEIL'; - FSQLSpecifities[spFuncLeft] := 'SUBSTR(%s, 1, %d)'; - FSQLSpecifities[spFuncNow] := 'DATETIME()'; - FSQLSpecifities[spFuncLastAutoIncNumber] := 'LAST_INSERT_ID()'; - FSQLSpecifities[spLockedTables] := ''; - FSQLSpecifities[spDisableForeignKeyChecks] := ''; - FSQLSpecifities[spEnableForeignKeyChecks] := ''; + Log(lcInfo, f_('Connecting to %s via %s, cipher %s, using encryption key: %s ...', + [FParameters.Hostname, FParameters.NetTypeName(True), FParameters.Username, UsingPass] + )); end; - ngInterbase: begin - FSQLSpecifities[spDatabaseDrop] := 'DROP DATABASE %s'; - FSQLSpecifities[spEmptyTable] := 'TRUNCATE '; - FSQLSpecifities[spRenameTable] := 'RENAME TABLE %s TO %s'; - FSQLSpecifities[spRenameView] := FSQLSpecifities[spRenameTable]; - if Self.Parameters.LibraryOrProvider = 'IB' then - FSQLSpecifities[spCurrentUserHost] := 'select user from rdb$database' - else - FSQLSpecifities[spCurrentUserHost] := 'select current_user || ''@'' || mon$attachments.mon$remote_host from mon$attachments where mon$attachments.mon$attachment_id = current_connection'; - FSQLSpecifities[spLikeCompare] := '%s LIKE %s'; - FSQLSpecifities[spAddColumn] := 'ADD COLUMN %s'; - FSQLSpecifities[spChangeColumn] := 'CHANGE COLUMN %s %s'; - FSQLSpecifities[spRenameColumn] := ''; - FSQLSpecifities[spSessionVariables] := 'SHOW VARIABLES'; - FSQLSpecifities[spGlobalVariables] := 'SHOW GLOBAL VARIABLES'; - FSQLSpecifities[spISSchemaCol] := '%s_SCHEMA'; - FSQLSpecifities[spUSEQuery] := ''; - FSQLSpecifities[spKillQuery] := 'KILL %d'; - FSQLSpecifities[spKillProcess] := 'KILL %d'; - FSQLSpecifities[spFuncLength] := 'LENGTH'; - FSQLSpecifities[spFuncCeil] := 'CEIL'; - FSQLSpecifities[spFuncLeft] := 'SUBSTR(%s, 1, %d)'; - FSQLSpecifities[spFuncNow] := ' cast(''now'' as timestamp) from rdb$database'; - FSQLSpecifities[spFuncLastAutoIncNumber] := 'LAST_INSERT_ID()'; - FSQLSpecifities[spLockedTables] := ''; - FSQLSpecifities[spDisableForeignKeyChecks] := ''; - FSQLSpecifities[spEnableForeignKeyChecks] := ''; + else begin + Log(lcInfo, f_('Connecting to %s via %s, username %s, using password: %s ...', + [FParameters.Hostname, FParameters.NetTypeName(True), FParameters.Username, UsingPass] + )); end; + end; + // Create SQL provider + case FParameters.NetTypeGroup of + ngMySQL: + FSqlProvider := TMySqlProvider.Create(FParameters.NetType); + ngMSSQL: + FSqlProvider := TMsSqlProvider.Create(FParameters.NetType); + ngPgSQL: + FSqlProvider := TPostgreSQLProvider.Create(FParameters.NetType); + ngSQLite: + FSqlProvider := TSQLiteProvider.Create(FParameters.NetType); + ngInterbase: + FSqlProvider := TInterbaseProvider.Create(FParameters.NetType); + else + raise Exception.CreateFmt(_(MsgUnhandledNetType), [Integer(FParameters.NetType)]); end; end; @@ -3195,7 +3307,7 @@ procedure TMySQLConnection.DoBeforeConnect; LibraryPath: String; begin // Init libmysql before actually connecting. - LibraryPath := ExtractFilePath(ParamStr(0)) + Parameters.LibraryOrProvider; + LibraryPath := GetAppDir + Parameters.LibraryOrProvider; Log(lcDebug, f_('Loading library file %s ...', [LibraryPath])); // Throws EDbError on any failure: FLib := TMySQLLib.Create(LibraryPath, Parameters.DefaultLibrary); @@ -3210,7 +3322,7 @@ procedure TPgConnection.DoBeforeConnect; msg: String; begin // Init lib before actually connecting. - LibraryPath := ExtractFilePath(ParamStr(0)) + Parameters.LibraryOrProvider; + LibraryPath := GetAppDir + Parameters.LibraryOrProvider; Log(lcDebug, f_('Loading library file %s ...', [LibraryPath])); try FLib := TPostgreSQLLib.Create(LibraryPath, Parameters.DefaultLibrary); @@ -3241,10 +3353,13 @@ procedure TSQLiteConnection.DoBeforeConnect; LibraryPath: String; begin // Init lib before actually connecting. - LibraryPath := ExtractFilePath(ParamStr(0)) + Parameters.LibraryOrProvider; + LibraryPath := GetAppDir + Parameters.LibraryOrProvider; Log(lcDebug, f_('Loading library file %s ...', [LibraryPath])); // Throws EDbError on any failure: - FLib := TSQLiteLib.Create(LibraryPath, Parameters.DefaultLibrary); + if Parameters.NetType = ntSQLite then + FLib := TSQLiteLib.Create(LibraryPath, Parameters.DefaultLibrary) + else + FLib := TSQLiteLib.CreateWithMultipleCipherFunctions(LibraryPath, Parameters.DefaultLibrary); Log(lcDebug, FLib.DllFile + ' v' + ServerVersionUntouched + ' loaded.'); inherited; end; @@ -3280,8 +3395,60 @@ procedure TDBConnection.EndSSHTunnel; procedure TDBConnection.DoAfterConnect; var + i: Integer; + TypeOid: String; + AllEnums: TDBQuery; + AllEnumsList: TStringList; SQLFunctionsFileOrder: String; + MajorMinorVer, MajorVer: String; + StartupScript: String; + StartupBatch: TSQLBatch; + SqlQuery: TSQLSentence; + TZI: TTimeZoneInformation; + Minutes, Hours: Integer; + Offset: String; begin + FSqlProvider.ServerVersion := ServerVersionInt; + + for i:=0 to High(Datatypes) do begin + + if Datatypes[i].NativeTypes = '?' then begin + // PG oid is set to be populated via '?' + TypeOid := GetVar('SELECT oid FROM '+QuoteIdent('pg_type')+' WHERE '+QuoteIdent('typname')+' = '+EscapeString(Datatypes[i].Name.ToLower)); + if IsNumeric(TypeOid) then begin + Datatypes[i].NativeTypes := TypeOid; + Log(lcInfo, 'Found oid/NativeTypes of '+Datatypes[i].Name+' data type: '+Datatypes[i].NativeTypes); + end + else begin + Log(lcInfo, 'No support for '+Datatypes[i].Name+' data type on this server.'); + end; + end + + else if (Datatypes[i].NativeTypes = 'e') and FSqlProvider.Has(qGetEnumTypes) then begin + // PG ENUM types populated via 'e' + AllEnums := GetResults(FSqlProvider.GetSql(qGetEnumTypes)); + AllEnumsList := TStringList.Create; + while not AllEnums.Eof do begin + AllEnumsList.Add(AllEnums.Col('enum_name')); + AllEnumsList.Add(AllEnums.Col('enum_schema') + '.' + AllEnums.Col('enum_name')); + FNamedEnums.AddPair( + AllEnums.Col('enum_name'), + AllEnums.Col('enum_labels') + ); + FNamedEnums.AddPair( + AllEnums.Col('enum_schema') + '.' + AllEnums.Col('enum_name'), + AllEnums.Col('enum_labels') + ); + AllEnums.Next; + end; + AllEnums.Free; + Datatypes[i].Names := Implode('|', AllEnumsList); + AllEnumsList.Free; + end; + + end; + + AppSettings.SessionPath := FParameters.SessionPath; AppSettings.WriteString(asServerVersionFull, FServerVersionUntouched); FParameters.ServerVersion := FServerVersionUntouched; @@ -3293,37 +3460,29 @@ procedure TDBConnection.DoAfterConnect; FKeepAliveTimer.OnTimer := KeepAliveTimerEvent; end; + MajorMinorVer := RegExprGetMatch('^(\d+\.\d+)', ServerVersionStr, 1); + MajorVer := RegExprGetMatch('^(\d+)\.', ServerVersionStr, 1); + if FParameters.IsMariaDB then - SQLFunctionsFileOrder := 'mariadb,mysql' + SQLFunctionsFileOrder := 'mariadb'+MajorMinorVer+',mariadb'+MajorVer+',mariadb,mysql' else if FParameters.IsAnyMySQL then - SQLFunctionsFileOrder := 'mysql' + SQLFunctionsFileOrder := 'mysql'+MajorMinorVer+',mysql'+MajorVer+',mysql' else if FParameters.IsRedshift then - SQLFunctionsFileOrder := 'redshift,postgresql' + SQLFunctionsFileOrder := 'redshift'+MajorMinorVer+',redshift'+MajorVer+',redshift,postgresql' else if FParameters.IsAnyPostgreSQL then - SQLFunctionsFileOrder := 'postgresql' + SQLFunctionsFileOrder := 'postgresql'+MajorMinorVer+',postgresql'+MajorVer+',postgresql' else if FParameters.IsAnyMSSQL then - SQLFunctionsFileOrder := 'mssql' + SQLFunctionsFileOrder := 'mssql'+MajorMinorVer+',mssql'+MajorVer+',mssql' else if FParameters.IsAnySQLite then - SQLFunctionsFileOrder := 'sqlite' + SQLFunctionsFileOrder := 'sqlite'+MajorMinorVer+',sqlite'+MajorVer+',sqlite' else if FParameters.IsAnyInterbase then - SQLFunctionsFileOrder := 'interbase' + SQLFunctionsFileOrder := 'interbase'+MajorMinorVer+',interbase'+MajorVer+',interbase' else SQLFunctionsFileOrder := ''; FSQLFunctions := TSQLFunctionList.Create(Self, SQLFunctionsFileOrder); -end; - - -procedure TMySQLConnection.DoAfterConnect; -var - TZI: TTimeZoneInformation; - Minutes, Hours, i: Integer; - Offset: String; - ObjNames: TStringList; -begin - inherited; // Set timezone offset to UTC - if (ServerVersionInt >= 40103) and Parameters.LocalTimeZone then begin + if FSqlProvider.Has(qSetTimezone) and Parameters.LocalTimeZone then begin Minutes := 0; case GetTimeZoneInformation(TZI) of TIME_ZONE_ID_STANDARD: Minutes := (TZI.Bias + TZI.StandardBias); @@ -3338,22 +3497,44 @@ procedure TMySQLConnection.DoAfterConnect; else Offset := '-'; Offset := Offset + Format('%.2d:%.2d', [Abs(Hours), Abs(Minutes)]); - Query('SET time_zone='+EscapeString(Offset)); + Query(qSetTimezone, [EscapeString(Offset)]); + end; + + // Process startup script + StartupScript := Trim(FParameters.StartupScriptFilename); + if StartupScript <> '' then begin + StartupScript := ExpandFileName(StartupScript); + if not FileExists(StartupScript) then + Log(lcError, f_('Startup script file not found: %s', [StartupScript])) + else begin + StartupBatch := TSQLBatch.Create(FParameters.NetTypeGroup); + StartupBatch.SQL := ReadTextfile(StartupScript, nil); + for SqlQuery in StartupBatch do try + Query(SqlQuery.SQL); + except + // Suppress popup, errors get logged into SQL log + end; + StartupBatch.Free; + end; end; +end; + + +procedure TMySQLConnection.DoAfterConnect; +var + ObjNames: TStringList; + i: Integer; +begin + inherited; // Support microseconds in some temporal datatypes of MariaDB 5.3+ and MySQL 5.6 - if ((ServerVersionInt >= 50300) and Parameters.IsMariaDB) or - ((ServerVersionInt >= 50604) and (not Parameters.IsMariaDB)) then begin + if Has(frTemporalTypesFraction) then begin for i:=Low(FDatatypes) to High(FDatatypes) do begin if FDatatypes[i].Index in [dbdtDatetime, dbdtDatetime2, dbdtTime, dbdtTimestamp] then FDatatypes[i].HasLength := True; end; end; - if (ServerVersionInt >= 50000) and (not Parameters.IsMySQLonRDS) then begin - FSQLSpecifities[spKillQuery] := 'KILL QUERY %d'; - end; - // List of IS tables try ObjNames := GetCol('SHOW TABLES FROM '+QuoteIdent(FInfSch)); @@ -3361,34 +3542,12 @@ procedure TMySQLConnection.DoAfterConnect; ObjNames.Free; except // silently fail if IS does not exist, on super old servers end; - - if (ServerVersionInt >= 50124) and (not Parameters.IsProxySQLAdmin) then - FSQLSpecifities[spLockedTables] := 'SHOW OPEN TABLES FROM %s WHERE '+QuoteIdent('in_use')+'!=0'; end; procedure TAdoDBConnection.DoAfterConnect; begin inherited; - // See http://sqlserverbuilds.blogspot.de/ - case ServerVersionInt of - 0..899: begin - FSQLSpecifities[spDatabaseTable] := QuoteIdent('master')+'..'+QuoteIdent('sysdatabases'); - FSQLSpecifities[spDatabaseTableId] := QuoteIdent('dbid'); - FSQLSpecifities[spDbObjectsTable] := '..'+QuoteIdent('sysobjects'); - FSQLSpecifities[spDbObjectsCreateCol] := 'crdate'; - FSQLSpecifities[spDbObjectsUpdateCol] := ''; - FSQLSpecifities[spDbObjectsTypeCol] := 'xtype'; - end; - else begin - FSQLSpecifities[spDatabaseTable] := QuoteIdent('sys')+'.'+QuoteIdent('databases'); - FSQLSpecifities[spDatabaseTableId] := QuoteIdent('database_id'); - FSQLSpecifities[spDbObjectsTable] := '.'+QuoteIdent('sys')+'.'+QuoteIdent('objects'); - FSQLSpecifities[spDbObjectsCreateCol] := 'create_date'; - FSQLSpecifities[spDbObjectsUpdateCol] := 'modify_date'; - FSQLSpecifities[spDbObjectsTypeCol] := 'type'; - end; - end; // List of known IS tables FInformationSchemaObjects.CommaText := 'CHECK_CONSTRAINTS,'+ 'COLUMN_DOMAIN_USAGE,'+ @@ -3465,6 +3624,11 @@ function TAdoDBConnection.Ping(Reconnect: Boolean): Boolean; if Reconnect then Active := True; end; + end + else begin + // Not active currently, reconnect + if Reconnect then + Active := True; end; Result := FActive; // Restart keep-alive timer @@ -3498,6 +3662,11 @@ function TPGConnection.Ping(Reconnect: Boolean): Boolean; if Reconnect then Active := True; end; + end + else begin + // Not active currently, reconnect + if Reconnect then + Active := True; end; Result := FActive; // Restart keep-alive timer @@ -3531,6 +3700,11 @@ function TInterbaseConnection.Ping(Reconnect: Boolean): Boolean; Log(lcDebug, 'Ping server ...'); if FActive then begin FFDHandle.Ping; + end + else begin + // Not active currently, reconnect + if Reconnect then + Active := True; end; Result := FActive; // Restart keep-alive timer @@ -3542,7 +3716,7 @@ function TInterbaseConnection.Ping(Reconnect: Boolean): Boolean; procedure TDBConnection.KeepAliveTimerEvent(Sender: TObject); begin // Ping server in intervals, without automatically reconnecting - if Active and (FLockedByThread = nil) then + if Active and (not IsLockedByThread) then Ping(False); end; @@ -3552,7 +3726,7 @@ procedure TDBConnection.KeepAliveTimerEvent(Sender: TObject); } procedure TDBConnection.Query(SQL: String; DoStoreResult: Boolean=False; LogCategory: TDBLogCategory=lcSQL); begin - if (FLockedByThread <> nil) and (FLockedByThread.ThreadID <> GetCurrentThreadID) then begin + if IsLockedByThread and (FLockedByThread.ThreadID <> GetCurrentThreadID) then begin Log(lcDebug, _('Waiting for running query to finish ...')); try FLockedByThread.WaitFor; @@ -3569,6 +3743,17 @@ procedure TDBConnection.Query(SQL: String; DoStoreResult: Boolean=False; LogCate end; +procedure TDBConnection.Query(QueryId: TQueryId); +begin + Query(FSqlProvider.GetSql(QueryId)); +end; + +procedure TDBConnection.Query(QueryId: TQueryId; const Args: array of const); +begin + Query(FSqlProvider.GetSql(QueryId, Args)); +end; + + procedure TMySQLConnection.Query(SQL: String; DoStoreResult: Boolean=False; LogCategory: TDBLogCategory=lcSQL); var QueryStatus: Integer; @@ -3841,6 +4026,9 @@ procedure TInterbaseConnection.Query(SQL: String; DoStoreResult: Boolean=False; SetLength(FLastRawResults, 0); FdQuery := TFDQuery.Create(Self); FdQuery.Connection := FFDHandle; + // Disable paging with 50 rows + FdQuery.FetchOptions.Mode := fmAll; + FdQuery.FetchOptions.RecsMax := -1; // Todo: suppress mouse cursor updates try FdQuery.ResourceOptions.CmdExecTimeout := Parameters.QueryTimeout; @@ -3891,7 +4079,7 @@ function TAdoDBConnection.GetLastResults: TDBQueryList; Batch: TSQLBatch; begin Result := TDBQueryList.Create(False); - Batch := TSQLBatch.Create; + Batch := TSQLBatch.Create(FParameters.NetTypeGroup); Batch.SQL := FLastQuerySQL; for i:=Low(FLastRawResults) to High(FLastRawResults) do begin r := Parameters.CreateQuery(Self); @@ -3925,7 +4113,75 @@ function TMySQLConnection.GetCreateCode(Obj: TDBObject): String; end; +function TPgConnection.GetCreateCode(Obj: TDBObject): String; +var + ProcDetails: TDBQuery; + DataType: String; + ArgNames, ArgTypes, Arguments: TStringList; + i: Integer; +begin + Result := ''; + case Obj.NodeType of + lntView: begin + // Prefer pg_catalog tables. See http://www.heidisql.com/forum.php?t=16213#p16685 + Result := 'CREATE VIEW ' + QuoteIdent(Obj.Name) + ' AS '; + if not Obj.IsMaterialized then begin // normal view + Result := Result + GetVar('SELECT '+QuoteIdent('definition')+ + ' FROM '+QuoteIdent('pg_views')+ + ' WHERE '+QuoteIdent('viewname')+'='+EscapeString(Obj.Name)+ + ' AND '+QuoteIdent('schemaname')+'='+EscapeString(Obj.Schema) + ); + end + else begin // materialized view + Result := Result + GetVar('SELECT '+QuoteIdent('definition')+ + ' FROM '+QuoteIdent('pg_matviews')+ + ' WHERE '+QuoteIdent('matviewname')+'='+EscapeString(Obj.Name)+ + ' AND '+QuoteIdent('schemaname')+'='+EscapeString(Obj.Schema) + ); + end; + + end; + lntFunction, lntProcedure: begin + Result := 'CREATE '+Obj.GetObjType.ToUpper+' '+QuoteIdent(Obj.Name); + ProcDetails := GetResults('SELECT '+ + QuoteIdent('p')+'.'+QuoteIdent('prosrc')+', '+ + QuoteIdent('p')+'.'+QuoteIdent('proargnames')+', '+ + QuoteIdent('p')+'.'+QuoteIdent('proargtypes')+', '+ + QuoteIdent('p')+'.'+QuoteIdent('prorettype')+' '+ + 'FROM '+QuoteIdent('pg_catalog')+'.'+QuoteIdent('pg_namespace')+' AS '+QuoteIdent('n')+' '+ + 'JOIN '+QuoteIdent('pg_catalog')+'.'+QuoteIdent('pg_proc')+' AS '+QuoteIdent('p')+' ON '+QuoteIdent('p')+'.'+QuoteIdent('pronamespace')+' = '+QuoteIdent('n')+'.'+QuoteIdent('oid')+' '+ + 'WHERE '+ + QuoteIdent('n')+'.'+QuoteIdent('nspname')+'='+EscapeString(Obj.Database)+ + 'AND '+QuoteIdent('p')+'.'+QuoteIdent('proname')+'='+EscapeString(Obj.Name)+ + 'AND '+QuoteIdent('p')+'.'+QuoteIdent('proargtypes')+'='+EscapeString(Obj.ArgTypes) + ); + ArgNames := Explode(',', Copy(ProcDetails.Col('proargnames'), 2, Length(ProcDetails.Col('proargnames'))-2)); + ArgTypes := Explode(' ', Copy(ProcDetails.Col('proargtypes'), 1, Length(ProcDetails.Col('proargtypes')))); + Arguments := TStringList.Create; + for i:=0 to ArgNames.Count-1 do begin + if ArgTypes.Count > i then + DataType := GetDatatypeByNativeType(MakeInt(ArgTypes[i]), ArgNames[i]).Name + else + DataType := ''; + Arguments.Add(ArgNames[i] + ' ' + DataType); + end; + Result := Result + '(' + Implode(', ', Arguments) + ') '+ + 'RETURNS '+GetDatatypeByNativeType(MakeInt(ProcDetails.Col('prorettype'))).Name+' '+ + 'AS $$ '+ProcDetails.Col('prosrc')+' $$' + // TODO: 'LANGUAGE SQL IMMUTABLE STRICT' + ; + end + else begin + // Let the generic method try to return code + Result := inherited; + end; + end; +end; + + function TSQLiteConnection.GetCreateCode(Obj: TDBObject): String; +var + CreateList: TStringList; begin // PRAGMA table_info(customers): // cid name type notnull dflt_value pk @@ -3933,8 +4189,15 @@ function TSQLiteConnection.GetCreateCode(Obj: TDBObject): String; // 1 FirstName NVARCHAR(40) 1 null 0 case Obj.NodeType of lntTable: begin + CreateList := GetCol('SELECT '+QuoteIdent('sql')+' FROM '+QuoteIdent(Obj.Database)+'.sqlite_master'+ + ' WHERE '+QuoteIdent('type')+' IN('+EscapeString('table')+', '+EscapeString('index')+')'+ + ' AND tbl_name='+EscapeString(Obj.Name)); + Result := Implode(';'+sLineBreak, CreateList); + CreateList.Free; + end; + lntView, lntTrigger: begin Result := GetVar('SELECT '+QuoteIdent('sql')+' FROM '+QuoteIdent(Obj.Database)+'.sqlite_master'+ - ' WHERE '+QuoteIdent('type')+'='+EscapeString('table')+ + ' WHERE '+QuoteIdent('type')+'='+EscapeString(Obj.ObjType.ToLower)+ ' AND name='+EscapeString(Obj.Name)); end; else begin @@ -4011,11 +4274,7 @@ function TMySQLConnection.GetCreateViewCode(Database, Name: String): String; function TDBConnection.GetCreateCode(Obj: TDBObject): String; var - ProcDetails: TDBQuery; - DataType: String; - ArgNames, ArgTypes, Arguments: TStringList; Rows: TStringList; - i: Integer; TableCols: TTableColumnList; TableCol: TTableColumn; TableKeys: TTableKeyList; @@ -4030,43 +4289,46 @@ function TDBConnection.GetCreateCode(Obj: TDBObject): String; Result := 'CREATE TABLE '+QuoteIdent(Obj.Name)+' ('; TableCols := Obj.GetTableColumns; for TableCol in TableCols do begin - Result := Result + CRLF + #9 + TableCol.SQLCode + ','; + Result := Result + sLineBreak + CodeIndent + TableCol.SQLCode + ','; end; TableCols.Free; TableKeys := Obj.GetTableKeys; for TableKey in TableKeys do begin - Result := Result + CRLF + #9 + TableKey.SQLCode + ','; + if TableKey.InsideCreateCode then + Result := Result + sLineBreak + CodeIndent + TableKey.SQLCode + ','; end; TableKeys.Free; TableForeignKeys := Obj.GetTableForeignKeys; for TableForeignKey in TableForeignKeys do begin - Result := Result + CRLF + #9 + TableForeignKey.SQLCode(True) + ','; + Result := Result + sLineBreak + CodeIndent + TableForeignKey.SQLCode(True) + ','; end; TableForeignKeys.Free; TableCheckConstraints := Obj.GetTableCheckConstraints; for TableCheckConstraint in TableCheckConstraints do begin - Result := Result + CRLF + #9 + TableCheckConstraint.SQLCode + ','; + Result := Result + sLineBreak + CodeIndent + TableCheckConstraint.SQLCode + ','; end; TableCheckConstraints.Free; Delete(Result, Length(Result), 1); - Result := Result + CRLF + ')'; + Result := Result + sLineBreak + ')'; + + TableKeys := Obj.GetTableKeys; + for TableKey in TableKeys do begin + if not TableKey.InsideCreateCode then begin + if TableKeys.IndexOf(TableKey) = 0 then + Result := Result + ';'; + Result := Result + sLineBreak + TableKey.SQLCode + ';'; + end; + end; + TableKeys.Free; end; lntView: begin case FParameters.NetTypeGroup of - ngPgSQL: begin - // Prefer pg_catalog tables. See http://www.heidisql.com/forum.php?t=16213#p16685 - Result := 'CREATE VIEW ' + QuoteIdent(Obj.Name) + ' AS ' + GetVar('SELECT '+QuoteIdent('definition')+ - ' FROM '+QuoteIdent('pg_views')+ - ' WHERE '+QuoteIdent('viewname')+'='+EscapeString(Obj.Name)+ - ' AND '+QuoteIdent('schemaname')+'='+EscapeString(Obj.Schema) - ); - end; ngMSSQL: begin // Overcome 4000 character limit in IS.VIEW_DEFINITION // See http://www.heidisql.com/forum.php?t=21097 @@ -4108,36 +4370,6 @@ function TDBConnection.GetCreateCode(Obj: TDBObject): String; Result := Implode('', Rows); Rows.Free; end; - ngPgSQL: begin - Result := 'CREATE FUNCTION '+QuoteIdent(Obj.Name); - ProcDetails := GetResults('SELECT '+ - QuoteIdent('p')+'.'+QuoteIdent('prosrc')+', '+ - QuoteIdent('p')+'.'+QuoteIdent('proargnames')+', '+ - QuoteIdent('p')+'.'+QuoteIdent('proargtypes')+', '+ - QuoteIdent('p')+'.'+QuoteIdent('prorettype')+' '+ - 'FROM '+QuoteIdent('pg_catalog')+'.'+QuoteIdent('pg_namespace')+' AS '+QuoteIdent('n')+' '+ - 'JOIN '+QuoteIdent('pg_catalog')+'.'+QuoteIdent('pg_proc')+' AS '+QuoteIdent('p')+' ON '+QuoteIdent('p')+'.'+QuoteIdent('pronamespace')+' = '+QuoteIdent('n')+'.'+QuoteIdent('oid')+' '+ - 'WHERE '+ - QuoteIdent('n')+'.'+QuoteIdent('nspname')+'='+EscapeString(Obj.Database)+ - 'AND '+QuoteIdent('p')+'.'+QuoteIdent('proname')+'='+EscapeString(Obj.Name)+ - 'AND '+QuoteIdent('p')+'.'+QuoteIdent('proargtypes')+'='+EscapeString(Obj.ArgTypes) - ); - ArgNames := Explode(',', Copy(ProcDetails.Col('proargnames'), 2, Length(ProcDetails.Col('proargnames'))-2)); - ArgTypes := Explode(' ', Copy(ProcDetails.Col('proargtypes'), 1, Length(ProcDetails.Col('proargtypes')))); - Arguments := TStringList.Create; - for i:=0 to ArgNames.Count-1 do begin - if ArgTypes.Count > i then - DataType := GetDatatypeByNativeType(MakeInt(ArgTypes[i]), ArgNames[i]).Name - else - DataType := ''; - Arguments.Add(ArgNames[i] + ' ' + DataType); - end; - Result := Result + '(' + Implode(', ', Arguments) + ') '+ - 'RETURNS '+GetDatatypeByNativeType(MakeInt(ProcDetails.Col('prorettype'))).Name+' '+ - 'AS $$ '+ProcDetails.Col('prosrc')+' $$' - // TODO: 'LANGUAGE SQL IMMUTABLE STRICT' - ; - end; else begin Result := GetVar('SELECT ROUTINE_DEFINITION'+ ' FROM '+InfSch+'.ROUTINES'+ @@ -4191,7 +4423,7 @@ procedure TDBConnection.PrefetchCreateCode(Objects: TDBObjectList); // SHOW CREATE TRIGGER was introduced in MySQL 5.1.21 // See #111 if Obj.NodeType = lntTrigger then - UseIt := UseIt and (ServerVersionInt >= 50121); + UseIt := UseIt and FSqlProvider.Has(qShowCreateTrigger); if UseIt then Queries.Add('SHOW CREATE '+UpperCase(Obj.ObjType)+' '+QuoteIdent(Obj.Database)+'.'+QuoteIdent(Obj.Name)); end; @@ -4223,7 +4455,6 @@ procedure TDBConnection.PrefetchCreateCode(Objects: TDBObjectList); procedure TDBConnection.SetDatabase(Value: String); var s: String; - UseQuery: String; begin Log(lcDebug, 'SetDatabase('+Value+'), FDatabase: '+FDatabase); if Value <> FDatabase then begin @@ -4243,9 +4474,8 @@ procedure TDBConnection.SetDatabase(Value: String); s := s + ', ' + EscapeString('public'); end else s := QuoteIdent(Value); - UseQuery := GetSQLSpecifity(spUSEQuery); - if not UseQuery.IsEmpty then begin - Query(GetSQLSpecifity(spUSEQuery, [s]), False); + if FSqlProvider.Has(qUSEQuery) then begin + Query(qUSEQuery, [s]); end; FDatabase := DeQuoteIdent(Value); if Assigned(FOnDatabaseChanged) then @@ -4276,7 +4506,7 @@ procedure TDBConnection.DetectUSEQuery(SQL: String); // Detect query for switching current working database or schema rx := TRegExpr.Create; rx.ModifierI := True; - rx.Expression := '^'+GetSQLSpecifity(spUSEQuery); + rx.Expression := '^'+FSqlProvider.GetSql(qUSEQuery); Quotes := QuoteRegExprMetaChars(FQuoteChars+''';'); rx.Expression := StringReplace(rx.Expression, ' ', '\s+', [rfReplaceAll]); rx.Expression := StringReplace(rx.Expression, '%s', '['+Quotes+']?([^'+Quotes+']+)['+Quotes+']*', [rfReplaceAll]); @@ -4385,17 +4615,21 @@ procedure TMySQLConnection.SetCharacterSet(CharsetName: String); FStatementNum := 0; Log(lcInfo, 'Changing character set from '+CharacterSet+' to '+CharsetName); Return := FLib.mysql_set_character_set(FHandle, PAnsiChar(Utf8Encode(CharsetName))); + // Return value never seems to be <> 0, not even on v3.23 servers, we check it anyway: if Return <> 0 then - raise EDbError.Create(LastErrorMsg) - else - FIsUnicode := CharsetName.StartsWith('utf', True); + raise EDbError.Create(LastErrorMsg); + // Check opt-out setting: if disabled, align the internal IsUnicode flag to the connection charset + if not FParameters.ForceUnicode then begin + FIsUnicode := CharacterSet.StartsWith('utf', True); + Log(lcInfo, 'ForceUnicode disabled in settings. Internal IsUnicode flag is now: ' + FIsUnicode.ToInteger.ToString) + end; end; procedure TPGConnection.SetCharacterSet(CharsetName: String); begin // See issue #22 - Query('SET CLIENT_ENCODING TO ' + EscapeString('UTF8')); + Query('SET CLIENT_ENCODING TO ' + EscapeString(CharsetName)); end; @@ -4615,27 +4849,41 @@ function TDBConnection.NdbClusterVersionInt: Integer; end; -function TDBConnection.GetAllDatabases: TStringList; +procedure TDBConnection.ShowWarnings; +begin + // Do nothing by default. SHOW WARNINGS is MySQL only. +end; + + +procedure TMySQLConnection.ShowWarnings; var - rx: TRegExpr; - dbname: String; + Warnings: TDBQuery; + Info: String; +begin + // Log warnings + // SHOW WARNINGS is implemented as of MySQL 4.1.0 + if (WarningCount > 0) and FSqlProvider.Has(qShowWarnings) then begin + Warnings := GetResults(FSqlProvider.GetSql(qShowWarnings)); + while not Warnings.Eof do begin + Log(lcError, _(Warnings.Col('Level')) + ': ('+Warnings.Col('Code')+') ' + Warnings.Col('Message')); + Warnings.Next; + end; + Warnings.Free; + end; + Info := DecodeAPIString(FLib.mysql_info(FHandle)); + if not Info.IsEmpty then begin + Log(lcInfo, _(SLogPrefixInfo) + ': ' + Info); + end; +end; + + +function TDBConnection.GetAllDatabases: TStringList; begin // Get user passed delimited list + // Ignore value in case of ntSQLiteEncrypted, when AllDatabasesStr holds encryption parameters if not Assigned(FAllDatabases) then begin - if FParameters.AllDatabasesStr <> '' then begin - FAllDatabases := TStringList.Create; - rx := TRegExpr.Create; - rx.Expression := '[^;]+'; - rx.ModifierG := True; - if rx.Exec(FParameters.AllDatabasesStr) then while true do begin - // Add if not a duplicate - dbname := Trim(rx.Match[0]); - if FAllDatabases.IndexOf(dbname) = -1 then - FAllDatabases.Add(dbname); - if not rx.ExecNext then - break; - end; - rx.Free; + if (FParameters.AllDatabasesStr <> '') and (not FParameters.IsAnySQLite) then begin + FAllDatabases := FParameters.AllDatabasesList; ApplyIgnoreDatabasePattern(FAllDatabases); end; end; @@ -4670,7 +4918,7 @@ function TAdoDBConnection.GetAllDatabases: TStringList; Result := inherited; if not Assigned(Result) then begin try - FAllDatabases := GetCol('SELECT '+QuoteIdent('name')+' FROM '+GetSQLSpecifity(spDatabaseTable)+' ORDER BY '+QuoteIdent('name')); + FAllDatabases := GetCol('SELECT '+QuoteIdent('name')+' FROM '+FSqlProvider.GetSql(qDatabaseTable)+' ORDER BY '+QuoteIdent('name')); except on E:EDbError do FAllDatabases := TStringList.Create; end; @@ -4801,7 +5049,7 @@ procedure TDBConnection.PrefetchResults(SQL: String); i: Integer; begin Query(SQL, True); - Batch := TSQLBatch.Create; + Batch := TSQLBatch.Create(FParameters.NetTypeGroup); Batch.SQL := SQL; FreeAndNil(FPrefetchResults); FPrefetchResults := TDBQueryList.Create(True); @@ -4857,7 +5105,7 @@ procedure TDBConnection.Log(Category: TDBLogCategory; Msg: String); begin // If in a thread, synchronize logging with the main thread. Logging within a thread // causes SynEdit to throw exceptions left and right. - if (FLockedByThread <> nil) and (FLockedByThread.ThreadID = GetCurrentThreadID) then begin + if IsLockedByThread and (FLockedByThread.ThreadID = GetCurrentThreadID) then begin (FLockedByThread as TQueryThread).LogFromThread(Msg, Category); Exit; end; @@ -4986,6 +5234,11 @@ function TDBConnection.EscapeString(Text: String; ProcessJokerChars: Boolean=fal if DoQuote then begin // Add surrounding single quotes Result := FStringQuoteChar + Result + FStringQuoteChar; + + // Support international characters with National prefix on MSSQL, see #1115, #2250, #41. + // Previously only done in some callers of EscapeString(), and only for column types dbdtNchar, dbdtNvarchar, dbdtNtext. + if FParameters.IsAnyMSSQL and (ServerVersionInt >= 1100) then + Result := 'N' + Result; end; end; @@ -4993,11 +5246,13 @@ function TDBConnection.EscapeString(Text: String; ProcessJokerChars: Boolean=fal function TDBConnection.EscapeString(Text: String; Datatype: TDBDatatype): String; var DoQuote: Boolean; + ValuePrefix: String; const CategoriesNeedQuote = [dtcText, dtcBinary, dtcTemporal, dtcSpatial, dtcOther]; begin // Quote text based on the passed datatype DoQuote := Datatype.Category in CategoriesNeedQuote; + ValuePrefix := ''; case Datatype.Category of // Some special cases dtcBinary: begin @@ -5007,11 +5262,13 @@ function TDBConnection.EscapeString(Text: String; Datatype: TDBDatatype): String dtcInteger, dtcReal: begin if (not IsNumeric(Text)) and (not IsHex(Text)) then DoQuote := True; - if Datatype.Index = dbdtBit then + if (Datatype.Index = dbdtBit) and FParameters.IsAnyMySQL then begin DoQuote := True; + ValuePrefix := 'b'; + end; end; end; - Result := EscapeString(Text, False, DoQuote); + Result := ValuePrefix + EscapeString(Text, False, DoQuote); end; @@ -5081,6 +5338,53 @@ function TDBConnection.UnescapeString(Text: String): String; end; +function TDBConnection.EscapeBin(BinValue: String): String; +var + BinLen: Integer; + Ansi: AnsiString; +begin + // Return a binary value as hex AnsiString + Ansi := AnsiString(BinValue); + BinLen := Length(Ansi); + if BinLen = 0 then begin + Result := EscapeString(''); + end else begin + if IsHex(BinValue) then begin + Result := BinValue; // Already hex encoded + end else begin + SetLength(Result, BinLen*2); + BinToHex(PAnsiChar(Ansi), PChar(Result), BinLen); + Result := '0x' + Result; + end; + if AppSettings.ReadBool(asLowercaseHex) then + Result := Result.ToLowerInvariant; + end; +end; + + +function TDBConnection.EscapeBin(var ByteData: TBytes): String; +var + BinLen: Integer; + Ansi: AnsiString; +begin + BinLen := Length(ByteData); + SetString(Ansi, PAnsiChar(ByteData), BinLen); + if BinLen = 0 then begin + Result := EscapeString(''); + end else begin + if IsHex(String(Ansi)) then begin + Result := String(Ansi); // Already hex encoded + end else begin + SetLength(Result, BinLen*2); + BinToHex(PAnsiChar(Ansi), PChar(Result), BinLen); + Result := '0x' + Result; + end; + if AppSettings.ReadBool(asLowercaseHex) then + Result := Result.ToLowerInvariant; + end; +end; + + function TDBConnection.ExtractLiteral(var SQL: String; Prefix: String): String; var i, LitStart: Integer; @@ -5169,8 +5473,9 @@ function TDBConnection.DeQuoteIdent(Identifier: String; Glue: Char=#0): String; function TDBConnection.CleanIdent(Identifier: string): string; begin Result := Trim(Identifier); - Result := LowerCase(Result); - Result := ReplaceRegExpr('[^a-z0-9]', Result, '_'); + // See issue #1947: + //Result := LowerCase(Result); + Result := ReplaceRegExpr('[^A-Za-z0-9]', Result, '_'); Result := ReplaceRegExpr('_+', Result, '_'); end; @@ -5321,40 +5626,14 @@ function TDBConnection.GetCollationTable: TDBQuery; begin Log(lcDebug, 'Fetching list of collations ...'); Ping(True); - Result := FCollationTable; -end; - - -function TMySQLConnection.GetCollationTable: TDBQuery; -begin - inherited; - if (not Assigned(FCollationTable)) and (ServerVersionInt >= 40100) then - FCollationTable := GetResults('SHOW COLLATION'); - if Assigned(FCollationTable) then - FCollationTable.First; - Result := FCollationTable; -end; - - -function TAdoDBConnection.GetCollationTable: TDBQuery; -begin - inherited; - if (not Assigned(FCollationTable)) then - FCollationTable := GetResults('SELECT '+EscapeString('')+' AS '+QuoteIdent('Collation')+', '+ - EscapeString('')+' AS '+QuoteIdent('Charset')+', 0 AS '+QuoteIdent('Id')+', '+ - EscapeString('')+' AS '+QuoteIdent('Default')+', '+EscapeString('')+' AS '+QuoteIdent('Compiled')+', '+ - '1 AS '+QuoteIdent('Sortlen')); - if Assigned(FCollationTable) then - FCollationTable.First; - Result := FCollationTable; -end; - - -function TInterbaseConnection.GetCollationTable: TDBQuery; -begin - inherited; - if not Assigned(FCollationTable) then begin - FCollationTable := GetResults('SELECT RDB$COLLATION_NAME AS '+QuoteIdent('Collation')+', RDB$COLLATION_ID AS '+QuoteIdent('Id')+', RDB$CHARACTER_SET_ID FROM RDB$COLLATIONS'); + if (not Assigned(FCollationTable)) and FSqlProvider.Has(qGetCollations) then begin + if FSqlProvider.Has(qGetCollationsExtended) then try + FCollationTable := GetResults(FSqlProvider.GetSql(qGetCollationsExtended)); + except + on E:EDbError do; + end; + if not Assigned(FCollationTable) then + FCollationTable := GetResults(FSqlProvider.GetSql(qGetCollations)); end; if Assigned(FCollationTable) then FCollationTable.First; @@ -5375,69 +5654,12 @@ function TDBConnection.GetCollationList: TStringList; end; -function TSQLiteConnection.GetCollationList: TStringList; -begin - // See https://www.sqlite.org/datatype3.html#collation_sequence_examples - Result := TStringList.Create; - Result.CommaText := 'nocase,binary,rtrim'; -end; - - function TDBConnection.GetCharsetTable: TDBQuery; begin Log(lcDebug, 'Fetching charset list ...'); Ping(True); - Result := nil; -end; - - -function TMySQLConnection.GetCharsetTable: TDBQuery; -begin - inherited; - if (not Assigned(FCharsetTable)) and (ServerVersionInt >= 40100) then - FCharsetTable := GetResults('SHOW CHARSET'); - Result := FCharsetTable; -end; - - -function TAdoDBConnection.GetCharsetTable: TDBQuery; -begin - inherited; - if not Assigned(FCharsetTable) then - FCharsetTable := GetResults('SELECT '+QuoteIdent('name')+' AS '+QuoteIdent('Charset')+', '+QuoteIdent('description')+' AS '+QuoteIdent('Description')+ - ' FROM '+QuotedDbAndTableName('master', 'syscharsets') - ); - Result := FCharsetTable; -end; - - -function TPgConnection.GetCharsetTable: TDBQuery; -begin - inherited; - if not Assigned(FCharsetTable) then - FCharsetTable := GetResults('SELECT PG_ENCODING_TO_CHAR('+QuoteIdent('encid')+') AS '+QuoteIdent('Charset')+', '+EscapeString('')+' AS '+QuoteIdent('Description')+' FROM ('+ - 'SELECT '+QuoteIdent('conforencoding')+' AS '+QuoteIdent('encid')+' FROM '+QuoteIdent('pg_conversion')+', '+QuoteIdent('pg_database')+' '+ - 'WHERE '+QuoteIdent('contoencoding')+'='+QuoteIdent('encoding')+' AND '+QuoteIdent('datname')+'=CURRENT_DATABASE()) AS '+QuoteIdent('e') - ); - Result := FCharsetTable; -end; - - -function TSQLiteConnection.GetCharsetTable; -begin - inherited; - if not Assigned(FCharsetTable) then begin - //FCharsetTable := // Todo! - end; - Result := FCharsetTable; -end; - - -function TInterbaseConnection.GetCharsetTable: TDBQuery; -begin - inherited; - if not Assigned(FCharsetTable) then - FCharsetTable := GetResults('SELECT RDB$CHARACTER_SET_NAME AS '+QuoteIdent('Charset')+', RDB$CHARACTER_SET_NAME AS '+QuoteIdent('Description')+' FROM RDB$CHARACTER_SETS'); + if (not Assigned(FCharsetTable)) and FSqlProvider.Has(qGetCharsets) then + FCharsetTable := GetResults(FSqlProvider.GetSql(qGetCharsets)); Result := FCharsetTable; end; @@ -5465,7 +5687,7 @@ function TDBConnection.GetSessionVariables(Refresh: Boolean): TDBQuery; if (not Assigned(FSessionVariables)) or Refresh then begin if Assigned(FSessionVariables) then FreeAndNil(FSessionVariables); - FSessionVariables := GetResults(GetSQLSpecifity(spSessionVariables)); + FSessionVariables := GetResults(FSqlProvider.GetSql(qSessionVariables)); end; FSessionVariables.First; Result := FSessionVariables; @@ -5520,7 +5742,7 @@ function TDBConnection.GetLockedTableCount(db: String): Integer; begin // Find tables which are currently locked. // Used to prevent waiting time in GetDBObjects. - sql := GetSQLSpecifity(spLockedTables); + sql := FSqlProvider.GetSql(qLockedTables); Result := 0; if not sql.IsEmpty then try LockedTables := GetCol(Format(sql, [QuoteIdent(db,False)])); @@ -5555,21 +5777,22 @@ function TDBConnection.IsTextDefault(Value: String; Tp: TDBDatatype): Boolean; // Inexact fallback detection, wrong if MariaDB allows "0+1" as expression at some point Result := Result or Value.IsEmpty or IsInt(Value[1]); end else if FParameters.IsAnyMySQL then begin - if ServerVersionInt <= 80013 then begin - // Only MySQL case with expression in default value is as follows: - if (Tp.Category = dtcTemporal) and Value.StartsWith('CURRENT_TIMESTAMP', True) then begin - Result := False; - end else begin - Result := True; - end; + // Only MySQL case with expression in default value is as follows: + if (Tp.Category = dtcTemporal) and Value.StartsWith('CURRENT_TIMESTAMP', True) then begin + Result := False; end - else begin - // https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html#data-type-defaults-explicit - // MySQL 8.0.13+ expect expressions to be wrapped in (..) when you create a table. - // But checking if first char is an opening parenthesis does not work here, as we get the expression - // from IS.COLUMNS, not from SHOW CREATE TABLE. So here's a workaround for distinguishing text - // from an expression: - Result := not Value.Contains('('); + else if Tp.Index = dbdtBit then + Result := False + else case ServerVersionInt of + 0..80013: Result := True; + else begin + // https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html#data-type-defaults-explicit + // MySQL 8.0.13+ expect expressions to be wrapped in (..) when you create a table. + // But checking if first char is an opening parenthesis does not work here, as we get the expression + // from IS.COLUMNS, not from SHOW CREATE TABLE. So here's a workaround for distinguishing text + // from an expression: + Result := not Value.Contains('('); + end; end; end else if FParameters.IsAnyPostgreSQL then begin // text only if starting with ' @@ -5586,26 +5809,43 @@ function TDBConnection.GetTableColumns(Table: TDBObject): TTableColumnList; TableIdx: Integer; ColQuery: TDBQuery; Col: TTableColumn; - dt, DefText, ExtraText, MaxLen: String; + dt, DefText, ExtraText, MaxLen, ColSQL: String; begin - // Generic: query table columns from IS.COLUMNS + // Generic: query table columns from IS.COLUMNS or query from provider Log(lcDebug, 'Getting fresh columns for '+Table.QuotedDbAndTableName); Result := TTableColumnList.Create(True); - TableIdx := InformationSchemaObjects.IndexOf('columns'); - if TableIdx = -1 then begin - // No is.columns table available - Exit; + + if FSqlProvider.Has(qGetTableColumns) then begin + ColSQL := FSqlProvider.GetSql(qGetTableColumns, [EscapeString(Table.Schema), EscapeString(Table.Name)]); + end + else begin + TableIdx := InformationSchemaObjects.IndexOf('columns'); + if TableIdx = -1 then begin + // No is.columns table available + Exit; + end; + ColSQL := 'SELECT * FROM '+QuoteIdent(InfSch)+'.'+QuoteIdent(InformationSchemaObjects[TableIdx])+ + ' WHERE '+Table.SchemaClauseIS('TABLE')+' AND TABLE_NAME='+EscapeString(Table.Name)+ + ' ORDER BY ORDINAL_POSITION'; end; - ColQuery := GetResults('SELECT * FROM '+QuoteIdent(InfSch)+'.'+QuoteIdent(InformationSchemaObjects[TableIdx])+ - ' WHERE '+Table.SchemaClauseIS('TABLE')+' AND TABLE_NAME='+EscapeString(Table.Name)+ - ' ORDER BY ORDINAL_POSITION'); + ColQuery := GetResults(ColSQL); + while not ColQuery.Eof do begin Col := TTableColumn.Create(Self); Result.Add(Col); Col.Name := ColQuery.Col('COLUMN_NAME'); Col.OldName := Col.Name; - // PG/MySQL use different fields: - dt := IfThen(ColQuery.ColExists('COLUMN_TYPE'), 'COLUMN_TYPE', 'DATA_TYPE'); + // MySQL and most commonly used field: + if ColQuery.ColExists('COLUMN_TYPE') then + dt := 'COLUMN_TYPE' + // PostgreSQL: + else if ColQuery.ColExists('DATA_TYPE') then begin + // user defined types, like CITEXT: + if (ColQuery.Col('DATA_TYPE').ToLower = 'user-defined') and ColQuery.ColExists('UDT_NAME') then + dt := 'UDT_NAME' + else + dt := 'DATA_TYPE'; + end; Col.ParseDatatype(ColQuery.Col(dt)); // PG/MSSQL don't include length in data type if Col.LengthSet.IsEmpty and Col.DataType.HasLength then begin @@ -5619,7 +5859,8 @@ function TDBConnection.GetTableColumns(Table: TDBObject): TTableColumnList; end; end; dtcInteger: begin - if not ColQuery.IsNull('NUMERIC_PRECISION') then begin + if (not ColQuery.IsNull('NUMERIC_PRECISION')) and Has(frIntegerDisplayWidth) then begin + // Integer display width is deprecated as of MySQL 8.0.17 MaxLen := ColQuery.Col('NUMERIC_PRECISION'); end; end; @@ -5646,24 +5887,26 @@ function TDBConnection.GetTableColumns(Table: TDBObject): TTableColumnList; Col.Collation := ColQuery.Col('COLLATION_NAME'); // MSSQL has no expression Col.GenerationExpression := ColQuery.Col('GENERATION_EXPRESSION', True); + Col.GenerationExpression := UnescapeString(Col.GenerationExpression); // PG has no extra: ExtraText := ColQuery.Col('EXTRA', True); Col.Virtuality := RegExprGetMatch('\b(\w+)\s+generated\b', ExtraText.ToLowerInvariant, 1); Col.Invisible := ExecRegExprI('\binvisible\b', ExtraText); Col.AllowNull := ColQuery.Col('IS_NULLABLE').ToLowerInvariant = 'yes'; + Col.SRID := StrToUIntDef(ColQuery.Col('SRS_ID', True), 0); DefText := ColQuery.Col('COLUMN_DEFAULT'); Col.OnUpdateType := cdtNothing; - if ColQuery.Col('COLUMN_DEFAULT').StartsWith('nextval(', True) then begin + if DefText.StartsWith('nextval(', True) then begin // PG auto increment Col.DefaultType := cdtAutoInc; - Col.DefaultText := ColQuery.Col('COLUMN_DEFAULT'); + Col.DefaultText := DefText; end else if ExecRegExpr('\bauto_increment\b', ExtraText.ToLowerInvariant) then begin // MySQL auto increment Col.DefaultType := cdtAutoInc; - Col.DefaultText := Col.AutoIncName; + Col.DefaultText := FSqlProvider.GetSql(qAutoInc); end else if DefText.ToLowerInvariant = 'null' then begin Col.DefaultType := cdtNull; @@ -5751,7 +5994,7 @@ function TMySQLConnection.GetTableColumns(Table: TDBObject): TTableColumnList; Col.OnUpdateType := cdtNothing; if ExecRegExpr('^auto_increment$', ExtraText.ToLowerInvariant) then begin Col.DefaultType := cdtAutoInc; - Col.DefaultText := Col.AutoIncName; + Col.DefaultText := FSqlProvider.GetSql(qAutoInc); end else if ColQuery.IsNull('Default') then begin Col.DefaultType := cdtNothing; end else if IsTextDefault(DefText, Col.DataType) then begin @@ -5815,34 +6058,6 @@ function TAdoDBConnection.GetTableColumns(Table: TDBObject): TTableColumnList; end; -function TPgConnection.GetTableColumns(Table: TDBObject): TTableColumnList; -var - Comments: TDBQuery; - TableCol: TTableColumn; -begin - Result := inherited; - // Column comments in Postgre. See issue #859 - // Todo: add current schema to WHERE clause? - Comments := GetResults('SELECT a.attname AS column, des.description AS comment'+ - ' FROM pg_attribute AS a, pg_description AS des, pg_class AS pgc'+ - ' WHERE'+ - ' pgc.oid = a.attrelid'+ - ' AND des.objoid = pgc.oid'+ - ' AND pg_table_is_visible(pgc.oid)'+ - ' AND pgc.relname = '+EscapeString(Table.Name)+ - ' AND a.attnum = des.objsubid' - ); - while not Comments.Eof do begin - for TableCol in Result do begin - if TableCol.Name = Comments.Col('column') then begin - TableCol.Comment := Comments.Col('comment'); - Break; - end; - end; - Comments.Next; - end; -end; - function TSQLiteConnection.GetTableColumns(Table: TDBObject): TTableColumnList; var ColQuery: TDBQuery; @@ -5852,7 +6067,7 @@ function TSQLiteConnection.GetTableColumns(Table: TDBObject): TTableColumnList; // Todo: include database name // Todo: default values Result := TTableColumnList.Create(True); - ColQuery := GetResults('SELECT * FROM '+QuoteIdent(Table.Database)+'.pragma_table_info('+EscapeString(Table.Name)+')'); + ColQuery := GetResults(FSqlProvider.GetSql(qGetTableColumns, [EscapeString(Table.Name), EscapeString(Table.Database)])); while not ColQuery.Eof do begin Col := TTableColumn.Create(Self); Result.Add(Col); @@ -5864,6 +6079,11 @@ function TSQLiteConnection.GetTableColumns(Table: TDBObject): TTableColumnList; Col.DefaultText := ''; Col.OnUpdateType := cdtNothing; Col.OnUpdateText := ''; + case StrToIntDef(ColQuery.Col('hidden'), 0) of + 1: Col.Invisible := True; + 2: Col.Virtuality := 'VIRTUAL'; + 3: Col.Virtuality := 'STORED'; + end; ColQuery.Next; end; ColQuery.Free; @@ -5877,24 +6097,7 @@ function TInterbaseConnection.GetTableColumns(Table: TDBObject): TTableColumnLis begin // Todo Result := TTableColumnList.Create(True); - ColQuery := GetResults('SELECT r.RDB$FIELD_NAME AS field_name,'+ - ' r.RDB$DESCRIPTION AS field_description,'+ - ' r.RDB$DEFAULT_VALUE AS field_default_value,'+ - ' r.RDB$NULL_FLAG AS null_flag,'+ - ' f.RDB$FIELD_LENGTH AS field_length,'+ - ' f.RDB$FIELD_PRECISION AS field_precision,'+ - ' f.RDB$FIELD_SCALE AS field_scale,'+ - ' f.RDB$FIELD_TYPE AS field_type,'+ - ' f.RDB$FIELD_SUB_TYPE AS field_subtype,'+ - ' coll.RDB$COLLATION_NAME AS field_collation,'+ - ' cset.RDB$CHARACTER_SET_NAME AS field_charset'+ - ' FROM RDB$RELATION_FIELDS r'+ - ' LEFT JOIN RDB$FIELDS f ON r.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME'+ - ' LEFT JOIN RDB$CHARACTER_SETS cset ON f.RDB$CHARACTER_SET_ID = cset.RDB$CHARACTER_SET_ID'+ - ' LEFT JOIN RDB$COLLATIONS coll ON f.RDB$COLLATION_ID = coll.RDB$COLLATION_ID'+ - ' AND F.RDB$CHARACTER_SET_ID = COLL.RDB$CHARACTER_SET_ID'+ - ' WHERE r.RDB$RELATION_NAME='+EscapeString(Table.Name)+ - ' ORDER BY r.RDB$FIELD_POSITION'); + ColQuery := GetResults(FSqlProvider.GetSql(qGetTableColumns, [EscapeString(Table.Name)])); while not ColQuery.Eof do begin Col := TTableColumn.Create(Self); Result.Add(Col); @@ -5949,6 +6152,7 @@ function TDBConnection.GetTableKeys(Table: TDBObject): TTableKeyList; end; NewKey.Columns.Add(KeyQuery.Col('COLUMN_NAME')); NewKey.SubParts.Add(''); + NewKey.Collations.Add(''); end; KeyQuery.Next; end; @@ -5980,6 +6184,7 @@ function TMySQLConnection.GetTableKeys(Table: TDBObject): TTableKeyList; end; NewKey.Columns.Add(ColQuery.Col('name')); NewKey.SubParts.Add(''); + NewKey.Collations.Add(''); ColQuery.Next; end; ColQuery.Free; @@ -5999,6 +6204,7 @@ function TMySQLConnection.GetTableKeys(Table: TDBObject): TTableKeyList; while not ColQuery.Eof do begin NewKey.Columns.Add(ColQuery.Col('name')); NewKey.SubParts.Add(''); + NewKey.Collations.Add(''); ColQuery.Next; end; ColQuery.Free; @@ -6008,7 +6214,7 @@ function TMySQLConnection.GetTableKeys(Table: TDBObject): TTableKeyList; end else begin - KeyQuery := GetResults('SHOW INDEXES FROM '+QuoteIdent(Table.Name)+' FROM '+QuoteIdent(Table.Database)); + KeyQuery := GetResults('SHOW KEYS FROM '+QuoteIdent(Table.Name)+' FROM '+QuoteIdent(Table.Database)); NewKey := nil; while not KeyQuery.Eof do begin if (not Assigned(NewKey)) or (NewKey.Name <> KeyQuery.Col('Key_name')) then begin @@ -6024,16 +6230,30 @@ function TMySQLConnection.GetTableKeys(Table: TDBObject): TTableKeyList; NewKey.IndexType := TTableKey.FULLTEXT else if CompareText(KeyQuery.Col('Index_type'), TTableKey.SPATIAL) = 0 then NewKey.IndexType := TTableKey.SPATIAL + else if CompareText(KeyQuery.Col('Index_type'), TTableKey.VECTOR) = 0 then + NewKey.IndexType := TTableKey.VECTOR else NewKey.IndexType := TTableKey.KEY; NewKey.OldIndexType := NewKey.IndexType; if ExecRegExpr('(BTREE|HASH)', KeyQuery.Col('Index_type')) then NewKey.Algorithm := KeyQuery.Col('Index_type'); NewKey.Comment := KeyQuery.Col('Index_comment', True); + if KeyQuery.ColumnExists('Visible') then // mysql 8 + NewKey.Visible := SameText(KeyQuery.Col('Visible'), 'yes') + else if KeyQuery.ColumnExists('Ignored') then // mariadb 10.6 + NewKey.Visible := SameText(KeyQuery.Col('Ignored'), 'NO'); + end; + if KeyQuery.ColumnExists('Expression') and (not KeyQuery.IsNull('Expression')) then begin + // Functional key part: enclose expression within parentheses to distinguish them from columns (issue #1777) + NewKey.Columns.Add('('+KeyQuery.Col('Expression')+')'); + end + else begin + // Normal column + NewKey.Columns.Add(KeyQuery.Col('Column_name')); end; - NewKey.Columns.Add(KeyQuery.Col('Column_name')); - if NewKey.IndexType = TTableKey.SPATIAL then - NewKey.SubParts.Add('') // Prevent "Incorrect prefix key" + NewKey.Collations.Add(KeyQuery.Col('Collation', True)); + if NewKey.IsSpatial then + NewKey.SubParts.Add('') // Keep in sync, prevent "Incorrect prefix key" else NewKey.SubParts.Add(KeyQuery.Col('Sub_part')); KeyQuery.Next; @@ -6107,6 +6327,7 @@ function TPGConnection.GetTableKeys(Table: TDBObject): TTableKeyList; end; NewKey.Columns.Add(KeyQuery.Col('COLUMN_NAME')); NewKey.SubParts.Add(''); + NewKey.Collations.Add(''); KeyQuery.Next; end; KeyQuery.Free; @@ -6120,7 +6341,7 @@ function TSQLiteConnection.GetTableKeys(Table: TDBObject): TTableKeyList; begin Result := TTableKeyList.Create(True); ColQuery := GetResults('SELECT * '+ - 'FROM '+QuoteIdent(Table.Database)+'.pragma_table_info('+EscapeString(Table.Name)+') '+ + 'FROM pragma_table_xinfo('+EscapeString(Table.Name)+', '+EscapeString(Table.Database)+') '+ 'WHERE pk!=0 ORDER BY pk'); NewKey := nil; while not ColQuery.Eof do begin @@ -6134,12 +6355,13 @@ function TSQLiteConnection.GetTableKeys(Table: TDBObject): TTableKeyList; end; NewKey.Columns.Add(ColQuery.Col('name')); NewKey.SubParts.Add(''); + NewKey.Collations.Add(''); ColQuery.Next; end; ColQuery.Free; KeyQuery := GetResults('SELECT * '+ - 'FROM '+QuoteIdent(Table.Database)+'.pragma_index_list('+EscapeString(Table.Name)+') '+ + 'FROM pragma_index_list('+EscapeString(Table.Name)+', '+EscapeString(Table.Database)+') '+ 'WHERE origin!='+EscapeString('pk')); while not KeyQuery.Eof do begin NewKey := TTableKey.Create(Self); @@ -6149,10 +6371,11 @@ function TSQLiteConnection.GetTableKeys(Table: TDBObject): TTableKeyList; NewKey.IndexType := IfThen(KeyQuery.Col('unique')='0', TTableKey.KEY, TTableKey.UNIQUE); NewKey.OldIndexType := NewKey.IndexType; ColQuery := GetResults('SELECT * '+ - 'FROM '+QuoteIdent(Table.Database)+'.pragma_index_info('+EscapeString(NewKey.Name)+')'); + 'FROM pragma_index_info('+EscapeString(NewKey.Name)+', '+EscapeString(Table.Database)+')'); while not ColQuery.Eof do begin NewKey.Columns.Add(ColQuery.Col('name')); NewKey.SubParts.Add(''); + NewKey.Collations.Add(''); ColQuery.Next; end; ColQuery.Free; @@ -6244,14 +6467,17 @@ function TAdoDbConnection.GetTableForeignKeys(Table: TDBObject): TForeignKeyList ForeignQuery := GetResults('SELECT'+ ' f.name AS foreign_key_name,'+ ' COL_NAME(fc.parent_object_id, fc.parent_column_id) AS constraint_column_name,'+ - ' OBJECT_NAME (f.referenced_object_id) AS referenced_object,'+ + ' SCHEMA_NAME(ro.schema_id) AS referenced_schema,'+ + ' OBJECT_NAME(f.referenced_object_id) AS referenced_object,'+ ' COL_NAME(fc.referenced_object_id, fc.referenced_column_id) AS referenced_column_name,'+ ' update_referential_action_desc,'+ ' delete_referential_action_desc'+ ' FROM sys.foreign_keys AS f'+ ' INNER JOIN sys.foreign_key_columns AS fc'+ ' ON f.object_id = fc.constraint_object_id'+ - ' WHERE f.parent_object_id = OBJECT_ID('+EscapeString(Table.Name)+')' + ' INNER JOIN sys.objects AS ro'+ + ' ON ro.object_id = f.referenced_object_id'+ + ' WHERE f.parent_object_id = OBJECT_ID('+EscapeString(Table.QuotedDbAndTableName)+')' ); ForeignKey := nil; while not ForeignQuery.Eof do begin @@ -6260,7 +6486,8 @@ function TAdoDbConnection.GetTableForeignKeys(Table: TDBObject): TForeignKeyList Result.Add(ForeignKey); ForeignKey.KeyName := ForeignQuery.Col('foreign_key_name'); ForeignKey.OldKeyName := ForeignKey.KeyName; - ForeignKey.ReferenceTable := ForeignQuery.Col('referenced_object'); + ForeignKey.ReferenceTable := + ForeignQuery.Col('referenced_schema') + '.' + ForeignQuery.Col('referenced_object'); ForeignKey.OnUpdate := ForeignQuery.Col('update_referential_action_desc'); ForeignKey.OnDelete := ForeignQuery.Col('delete_referential_action_desc'); end; @@ -6280,36 +6507,56 @@ function TPgConnection.GetTableForeignKeys(Table: TDBObject): TForeignKeyList; // see #158 Result := TForeignKeyList.Create(True); try - ForeignQuery := GetResults('SELECT'+ - ' refc.constraint_name,'+ - ' refc.update_rule,'+ - ' refc.delete_rule,'+ - ' kcu.table_name,'+ - ' STRING_AGG(distinct kcu.column_name, '','') AS columns,'+ - ' ccu.table_schema AS ref_schema,'+ - ' ccu.table_name AS ref_table,'+ - ' STRING_AGG(distinct ccu.column_name, '','') AS ref_columns,'+ - ' STRING_AGG(distinct kcu.ordinal_position::text, '','') AS ord_position'+ - ' FROM'+ - ' '+InfSch+'.referential_constraints AS refc,'+ - ' '+InfSch+'.key_column_usage AS kcu,'+ - ' '+InfSch+'.constraint_column_usage AS ccu'+ - ' WHERE'+ - ' refc.constraint_schema = '+EscapeString(Table.Schema)+ - ' AND refc.constraint_name = kcu.constraint_name'+ - ' AND refc.constraint_schema = kcu.table_schema'+ - ' AND ccu.constraint_name = refc.constraint_name'+ - ' AND kcu.table_name = '+EscapeString(Table.Name)+ - ' GROUP BY'+ - ' refc.constraint_name,'+ - ' refc.update_rule,'+ - ' refc.delete_rule,'+ - ' kcu.table_name,'+ - ' ccu.table_schema,'+ - ' ccu.table_name'+ - ' ORDER BY'+ - ' ord_position' - ); + ForeignQuery := GetResults( + 'SELECT ' + + ' con.conname AS constraint_name, ' + + ' CASE con.confupdtype ' + + ' WHEN ''a'' THEN ''NO ACTION'' ' + + ' WHEN ''r'' THEN ''RESTRICT'' ' + + ' WHEN ''c'' THEN ''CASCADE'' ' + + ' WHEN ''n'' THEN ''SET NULL'' ' + + ' WHEN ''d'' THEN ''SET DEFAULT'' ' + + ' END AS update_rule, ' + + ' CASE con.confdeltype ' + + ' WHEN ''a'' THEN ''NO ACTION'' ' + + ' WHEN ''r'' THEN ''RESTRICT'' ' + + ' WHEN ''c'' THEN ''CASCADE'' ' + + ' WHEN ''n'' THEN ''SET NULL'' ' + + ' WHEN ''d'' THEN ''SET DEFAULT'' ' + + ' END AS delete_rule, ' + + ' src_ns.nspname AS table_schema, ' + + ' src_tbl.relname AS table_name, ' + + ' string_agg(src_col.attname, '','' ORDER BY ord.pos) AS columns, ' + + ' ref_ns.nspname AS ref_schema, ' + + ' ref_tbl.relname AS ref_table, ' + + ' string_agg(ref_col.attname, '','' ORDER BY ord.pos) AS ref_columns, ' + + ' string_agg(ord.pos::text, '','' ORDER BY ord.pos) AS ord_position ' + + 'FROM pg_constraint con ' + + 'JOIN pg_class src_tbl ON src_tbl.oid = con.conrelid ' + + 'JOIN pg_namespace src_ns ON src_ns.oid = src_tbl.relnamespace ' + + 'JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS ord(attnum, pos) ON TRUE ' + + 'JOIN pg_attribute src_col ON src_col.attrelid = src_tbl.oid AND src_col.attnum = ord.attnum ' + + 'JOIN pg_class ref_tbl ON ref_tbl.oid = con.confrelid ' + + 'JOIN pg_namespace ref_ns ON ref_ns.oid = ref_tbl.relnamespace ' + + 'JOIN LATERAL unnest(con.confkey) WITH ORDINALITY AS ref_ord(attnum, pos) ' + + ' ON ref_ord.pos = ord.pos ' + + 'JOIN pg_attribute ref_col ON ref_col.attrelid = ref_tbl.oid AND ref_col.attnum = ref_ord.attnum ' + + 'WHERE ' + + ' con.contype = ''f'' ' + + ' AND src_ns.nspname = '+EscapeString(Table.Schema) + + ' AND src_tbl.relname = '+EscapeString(Table.Name) + + 'GROUP BY ' + + ' con.conname, ' + + ' con.confupdtype, ' + + ' con.confdeltype, ' + + ' src_ns.nspname, ' + + ' src_tbl.relname, ' + + ' ref_ns.nspname, ' + + ' ref_tbl.relname ' + + 'ORDER BY ' + + ' MIN(ord.pos)' + ); + while not ForeignQuery.Eof do begin ForeignKey := TForeignKey.Create(Self); Result.Add(ForeignKey); @@ -6343,7 +6590,7 @@ function TSQLiteConnection.GetTableForeignKeys(Table: TDBObject): TForeignKeyLis // SQLite: query PRAGMA foreign_key_list Result := TForeignKeyList.Create(True); ForeignQuery := GetResults('SELECT * '+ - 'FROM '+QuoteIdent(Table.Database)+'.pragma_foreign_key_list('+EscapeString(Table.Name)+')'); + 'FROM pragma_foreign_key_list('+EscapeString(Table.Name)+', '+EscapeString(Table.Database)+')'); ForeignKey := nil; while not ForeignQuery.Eof do begin if (not Assigned(ForeignKey)) or (ForeignKey.KeyName <> ForeignQuery.Col('id')) then begin @@ -6488,69 +6735,48 @@ function TDBConnection.IsHex(Text: String): Boolean; end; end; - -function TMySQLConnection.GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; -var - Rows: String; -begin - // Get row number from a mysql table - if Parameters.IsProxySQLAdmin or ForceExact then - Rows := GetVar('SELECT COUNT(*) FROM '+QuoteIdent(Obj.Database)+'.'+QuoteIdent(Obj.Name), 0) - else - Rows := GetVar('SHOW TABLE STATUS LIKE '+EscapeString(Obj.Name), 'Rows'); - Result := MakeInt(Rows); -end; - - -function TAdoDBConnection.GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; -var - Rows: String; +function TDBConnection.Has(Item: TFeatureOrRequirement): Boolean; begin - // Get row number from a mssql table - if ServerVersionInt >= 900 then begin - Rows := GetVar('SELECT SUM('+QuoteIdent('rows')+') FROM '+QuoteIdent('sys')+'.'+QuoteIdent('partitions')+ - ' WHERE '+QuoteIdent('index_id')+' IN (0, 1)'+ - ' AND '+QuoteIdent('object_id')+' = object_id('+EscapeString(Obj.Database+'.'+Obj.Schema+'.'+Obj.Name)+')' - ); - end else begin - Rows := GetVar('SELECT COUNT(*) FROM '+Obj.QuotedDbAndTableName); + case FParameters.NetTypeGroup of + ngMySQL: + case Item of + frSrid: Result := FParameters.IsMySQL(True) and (ServerVersionInt >= 80000); + frTemporalTypesFraction: Result := (FParameters.IsMariaDB and (ServerVersionInt >= 50300)) or + (FParameters.IsMySQL(True) and (ServerVersionInt >= 50604)); + frIntegerDisplayWidth: Result := (FParameters.IsMySQL(True) and (ServerVersionInt < 80017)) or + (not FParameters.IsMySQL(True)); + frColumnDefaultParentheses: Result := FParameters.IsMySQL(True) and (ServerVersionInt >= 80013); + frEditVariables: Result := ServerVersionInt >= 40003; + frCreateView: Result := ServerVersionInt >= 50001; + frCreateProcedure: Result := ServerVersionInt >= 50003; + frCreateFunction: Result := ServerVersionInt >= 50003; + frCreateTrigger: Result := ServerVersionInt >= 50002; + frCreateEvent: Result := ServerVersionInt >= 50100; + frInvisibleColumns: Result := (FParameters.IsMariaDB and (ServerVersionInt >= 100303)) or + (FParameters.IsMySQL(True) and (ServerVersionInt >= 80023)); + frCompressedColumns: Result := (FParameters.IsMariaDB and (ServerVersionInt >= 100301)); + end; + else Result := False; end; - Result := MakeInt(Rows); -end; - - -function TPgConnection.GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; -var - Rows: String; -begin - // Get row number from a postgres table - Rows := GetVar('SELECT '+QuoteIdent('reltuples')+'::bigint FROM '+QuoteIdent('pg_class')+ - ' LEFT JOIN '+QuoteIdent('pg_namespace')+ - ' ON ('+QuoteIdent('pg_namespace')+'.'+QuoteIdent('oid')+' = '+QuoteIdent('pg_class')+'.'+QuoteIdent('relnamespace')+')'+ - ' WHERE '+QuoteIdent('pg_class')+'.'+QuoteIdent('relkind')+'='+EscapeString('r')+ - ' AND '+QuoteIdent('pg_namespace')+'.'+QuoteIdent('nspname')+'='+EscapeString(Obj.Database)+ - ' AND '+QuoteIdent('pg_class')+'.'+QuoteIdent('relname')+'='+EscapeString(Obj.Name) - ); - Result := MakeInt(Rows); -end; - - -function TSQLiteConnection.GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; -var - Rows: String; -begin - // Get row number from a table - Rows := GetVar('SELECT COUNT(*) FROM '+QuoteIdent(Obj.Database)+'.'+QuoteIdent(Obj.Name), 0); - Result := MakeInt(Rows); end; -function TInterbaseConnection.GetRowCount(Obj: TDBObject; ForceExact: Bool=False): Int64; +function TDBConnection.GetRowCount(Obj: TDBObject; ForceExact: Boolean=False): Int64; var - Rows: String; + Rows, QueryApprox, QueryExact: String; + RowsColumn: Integer; begin // Get row number from a table - Rows := GetVar('SELECT COUNT(*) FROM '+QuoteIdent(Obj.Database)+'.'+QuoteIdent(Obj.Name), 0); + QueryApprox := FSqlProvider.GetSql(qGetRowCountApprox, Obj.AsStringMap); + if QueryApprox.IsEmpty or ForceExact then begin + QueryExact := FSqlProvider.GetSql(qGetRowCountExact, Obj.AsStringMap); + Rows := GetVar(QueryExact); + end + else begin + // This is ugly: in MySQL 4.x we only have SHOW TABLE STATUS, which cannot be limited to the "Rows" column + RowsColumn := IfThen(QueryApprox.StartsWith('SHOW ', True), 4, 0); + Rows := GetVar(QueryApprox, RowsColumn); + end; Result := MakeInt(Rows); end; @@ -6588,20 +6814,6 @@ procedure TPgConnection.Drop(Obj: TDBObject); end; -function TDBConnection.GetSQLSpecifity(Specifity: TSQLSpecifityId): String; -begin - // Return some version specific SQL clause or snippet - Result := FSQLSpecifities[Specifity]; -end; - - -function TDBConnection.GetSQLSpecifity(Specifity: TSQLSpecifityId; const Args: array of const): String; -begin - Result := GetSQLSpecifity(Specifity); - Result := Format(Result, Args); -end; - - function TDBConnection.ResultCount; begin case Parameters.NetTypeGroup of @@ -6659,8 +6871,8 @@ function TDBConnection.GetCurrentUserHostCombination: String; // Return current user@host combination, used by various object editors for DEFINER clauses Log(lcDebug, 'Fetching user@host ...'); Ping(True); - if FCurrentUserHostCombination.IsEmpty and (not GetSQLSpecifity(spCurrentUserHost).IsEmpty) then - FCurrentUserHostCombination := GetVar(GetSQLSpecifity(spCurrentUserHost)) + if FCurrentUserHostCombination.IsEmpty and (not FSqlProvider.GetSql(qCurrentUserHost).IsEmpty) then + FCurrentUserHostCombination := GetVar(FSqlProvider.GetSql(qCurrentUserHost)) else FCurrentUserHostCombination := ''; Result := FCurrentUserHostCombination; @@ -6902,6 +7114,7 @@ procedure TMySQLConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); obj: TDBObject; Results: TDBQuery; rx: TRegExpr; + SchemaBug41907Exists, DbNameMatches: Boolean; begin // Return a db's table list try @@ -6991,8 +7204,8 @@ procedure TMySQLConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); end; // Stored functions - if (ServerVersionInt >= 50000) and (not Parameters.IsProxySQLAdmin) then try - Results := GetResults('SHOW FUNCTION STATUS WHERE '+QuoteIdent('Db')+'='+EscapeString(db)); + if FSqlProvider.Has(qShowFunctionStatus) then try + Results := GetResults(FSqlProvider.GetSql(qShowFunctionStatus, [EscapeString(db)])); except on E:EDbError do; end; @@ -7012,8 +7225,8 @@ procedure TMySQLConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); end; // Stored procedures - if (ServerVersionInt >= 50000) and (not Parameters.IsProxySQLAdmin) then try - Results := GetResults('SHOW PROCEDURE STATUS WHERE '+QuoteIdent('Db')+'='+EscapeString(db)); + if FSqlProvider.Has(qShowProcedureStatus) then try + Results := GetResults(FSqlProvider.GetSql(qShowProcedureStatus, [EscapeString(db)])); except on E:EDbError do; end; @@ -7033,8 +7246,8 @@ procedure TMySQLConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); end; // Triggers - if (ServerVersionInt >= 50010) and (not Parameters.IsProxySQLAdmin) then try - Results := GetResults('SHOW TRIGGERS FROM '+QuoteIdent(db)); + if FSqlProvider.Has(qShowTriggers) then try + Results := GetResults(FSqlProvider.GetSql(qShowTriggers, [QuoteIdent(db)])); except on E:EDbError do; end; @@ -7053,9 +7266,8 @@ procedure TMySQLConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); end; // Events - if (ServerVersionInt >= 50100) and (not Parameters.IsProxySQLAdmin) then try - Results := GetResults('SELECT *, EVENT_SCHEMA AS '+QuoteIdent('Db')+', EVENT_NAME AS '+QuoteIdent('Name')+ - ' FROM '+InfSch+'.'+QuoteIdent('EVENTS')+' WHERE '+QuoteIdent('EVENT_SCHEMA')+'='+EscapeString(db)) + if FSqlProvider.Has(qShowEvents) then try + Results := GetResults(FSqlProvider.GetSql(qShowEvents, [EscapeString(db)])); except on E:EDbError do begin try @@ -7066,8 +7278,14 @@ procedure TMySQLConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); end; end; if Assigned(Results) then begin + // Work around old MySQL bug: https://bugs.mysql.com/bug.php?id=41907#c360194 + // "Noted [fixed] in 5.1.57, 5.5.12, 5.6.3 changelogs." + SchemaBug41907Exists := (ServerVersionInt < 50157) or + ((ServerVersionInt >= 50500) and (ServerVersionInt < 50512)) or + ((ServerVersionInt >= 50600) and (ServerVersionInt < 50603)); while not Results.Eof do begin - if Results.Col('Db') = db then begin + DbNameMatches := CompareText(Results.Col('Db'), db) = 0; + if (SchemaBug41907Exists and DbNameMatches) or (not SchemaBug41907Exists) then begin Obj := TDBObject.Create(Self); Cache.Add(obj); Obj.Name := Results.Col('Name'); @@ -7095,13 +7313,19 @@ procedure TAdoDBConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); // Tables, views and procedures Results := nil; // Schema support introduced in MSSQL 2005 (9.0). See issue #3212. + // RowsInTable added in 12.16 SchemaSelect := EscapeString(''); if ServerVersionInt >= 900 then SchemaSelect := 'SCHEMA_NAME('+QuoteIdent('schema_id')+')'; try - Results := GetResults('SELECT *, '+SchemaSelect+' AS '+EscapeString('schema')+ - ' FROM '+QuoteIdent(db)+GetSQLSpecifity(spDbObjectsTable)+ - ' WHERE '+QuoteIdent('type')+' IN ('+EscapeString('P')+', '+EscapeString('U')+', '+EscapeString('V')+', '+EscapeString('TR')+', '+EscapeString('FN')+', '+EscapeString('TF')+', '+EscapeString('IF')+')'); + Results := GetResults('SELECT o.*, '+SchemaSelect+' AS '+EscapeString('schema')+', rc.RowsInTable'+ + ' FROM '+QuoteIdent(db)+FSqlProvider.GetSql(qDbObjectsTable)+ ' AS o'+ + ' LEFT JOIN ('+ + ' SELECT object_id, SUM(rows) AS RowsInTable FROM '+QuoteIdent(db)+'.sys.partitions'+ + ' WHERE index_id IN (0,1)'+ // -- heap or clustered index + ' GROUP BY object_id'+ + ' ) AS rc ON rc.object_id = o.object_id'+ + ' WHERE o.'+QuoteIdent('type')+' IN ('+EscapeString('P')+', '+EscapeString('U')+', '+EscapeString('V')+', '+EscapeString('TR')+', '+EscapeString('FN')+', '+EscapeString('TF')+', '+EscapeString('IF')+')'); except on E:EDbError do; end; @@ -7110,11 +7334,11 @@ procedure TAdoDBConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); obj := TDBObject.Create(Self); Cache.Add(obj); obj.Name := Results.Col('name'); - obj.Created := ParseDateTime(Results.Col(GetSQLSpecifity(spDbObjectsCreateCol), True)); - obj.Updated := ParseDateTime(Results.Col(GetSQLSpecifity(spDbObjectsUpdateCol), True)); + obj.Created := ParseDateTime(Results.Col(FSqlProvider.GetSql(qDbObjectsCreateCol), True)); + obj.Updated := ParseDateTime(Results.Col(FSqlProvider.GetSql(qDbObjectsUpdateCol), True)); obj.Schema := Results.Col('schema'); obj.Database := db; - tp := Trim(Results.Col(GetSQLSpecifity(spDbObjectsTypeCol), True)); + tp := Trim(Results.Col(FSqlProvider.GetSql(qDbObjectsTypeCol), True)); if tp = 'U' then obj.NodeType := lntTable else if tp = 'P' then @@ -7125,6 +7349,8 @@ procedure TAdoDBConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); obj.NodeType := lntTrigger else if (tp = 'FN') or (tp = 'TF') or (tp = 'IF') then obj.NodeType := lntFunction; + obj.Rows := StrToInt64Def(Results.Col('RowsInTable'), -1); + obj.RowsAreExact := False; // approximate, not guaranteed exact. // Set reasonable default value for calculation of export chunks. See #343 // OFFSET..FETCH supported from v11.0/2012 // Disabled, leave at -1 and prefer a generic calculation in TfrmTableTools.DoExport @@ -7141,35 +7367,53 @@ procedure TPGConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); var obj: TDBObject; Results: TDBQuery; - tp, SchemaTable: String; - DataLenClause, IndexLenClause: String; + tp: String; + DataLenClause, IndexLenClause, ProKindClause: String; begin // Tables, views and procedures Results := nil; try - // See http://www.heidisql.com/forum.php?t=16429 - if ServerVersionInt >= 70300 then - SchemaTable := 'QUOTE_IDENT(t.TABLE_SCHEMA) || '+EscapeString('.')+' || QUOTE_IDENT(t.TABLE_NAME)' - else - SchemaTable := EscapeString(FQuoteChar)+' || t.TABLE_SCHEMA || '+EscapeString(FQuoteChar+'.'+FQuoteChar)+' || t.TABLE_NAME || '+EscapeString(FQuoteChar); // See http://www.heidisql.com/forum.php?t=16996 if Parameters.FullTableStatus and (ServerVersionInt >= 90000) then - DataLenClause := 'pg_table_size('+SchemaTable+')::bigint' + DataLenClause := 'pg_table_size(format(''%I.%I'', n.nspname, c.relname))::bigint' else DataLenClause := 'NULL'; // See https://www.heidisql.com/forum.php?t=34635 if Parameters.FullTableStatus and (ServerVersionInt >= 80100) then - IndexLenClause := 'pg_relation_size('+SchemaTable+')::bigint' + IndexLenClause := 'pg_relation_size(format(''%I.%I'', n.nspname, c.relname))::bigint' else IndexLenClause := 'relpages::bigint * '+SIZE_KB.ToString; - Results := GetResults('SELECT *,'+ - ' '+DataLenClause+' AS data_length,'+ - ' '+IndexLenClause+' AS index_length,'+ - ' c.reltuples, obj_description(c.oid) AS comment'+ - ' FROM '+QuoteIdent(InfSch)+'.'+QuoteIdent('tables')+' AS t'+ - ' LEFT JOIN '+QuoteIdent('pg_namespace')+' n ON t.table_schema = n.nspname'+ - ' LEFT JOIN '+QuoteIdent('pg_class')+' c ON n.oid = c.relnamespace AND c.relname=t.table_name'+ - ' WHERE t.'+QuoteIdent('table_schema')+'='+EscapeString(db) // Use table_schema when using schemata + if ServerVersionInt >= 110000 then + ProKindClause := 'p.prokind' + else + ProKindClause := EscapeString('p'); + Results := GetResults('SELECT '+ + ' n.nspname AS schema_name, '+ + ' c.relname AS object_name, '+ + ' c.relkind AS object_kind, '+ + ' '+DataLenClause+' AS data_length, '+ + ' '+IndexLenClause+' AS index_length, '+ + ' c.reltuples, '+ + ' obj_description(c.oid) AS comment, '+ + ' NULL AS proargtypes '+ + 'FROM pg_class c '+ + 'JOIN pg_namespace n ON n.oid = c.relnamespace '+ + 'WHERE n.nspname = '+EscapeString(db)+' '+ + ' AND c.relkind IN (''r'',''v'',''m'') '+ + 'UNION ALL '+ + 'SELECT '+ + ' n.nspname AS schema_name, '+ + ' p.proname AS object_name, '+ + ' '+ProKindClause+' AS object_kind, '+ + ' NULL::bigint AS data_length, '+ + ' NULL::bigint AS index_length, '+ + ' NULL::real AS reltuples, '+ + ' obj_description(p.oid) AS comment, '+ + ' p.proargtypes '+ + 'FROM pg_proc p '+ + 'JOIN pg_namespace n ON n.oid = p.pronamespace '+ + 'WHERE n.nspname = '+EscapeString(db)+' '+ + ' AND '+ProKindClause+' IN (''f'',''p'') ' ); except on E:EDbError do; @@ -7178,11 +7422,11 @@ procedure TPGConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); while not Results.Eof do begin obj := TDBObject.Create(Self); Cache.Add(obj); - obj.Name := Results.Col('table_name'); + obj.Name := Results.Col('object_name'); obj.Created := 0; obj.Updated := 0; obj.Database := db; - obj.Schema := Results.Col('table_schema'); // Remove when using schemata + obj.Schema := Results.Col('schema_name'); // Remove when using schemata obj.Comment := Results.Col('comment'); obj.Rows := StrToInt64Def(Results.Col('reltuples'), obj.Rows); obj.DataLen := StrToInt64Def(Results.Col('data_length'), obj.DataLen); @@ -7190,35 +7434,22 @@ procedure TPGConnection.FetchDbObjects(db: String; var Cache: TDBObjectList); obj.Size := obj.DataLen + obj.IndexLen; Inc(Cache.FDataSize, Obj.Size); Cache.FLargestObjectSize := Max(Cache.FLargestObjectSize, Obj.Size); - tp := Results.Col('table_type', True); - if tp = 'VIEW' then - obj.NodeType := lntView - else - obj.NodeType := lntTable; - Results.Next; - end; - FreeAndNil(Results); - end; - - // Stored functions. No procedures in PostgreSQL. - // See http://dba.stackexchange.com/questions/2357/what-are-the-differences-between-stored-procedures-and-stored-functions - try - Results := GetResults('SELECT '+QuoteIdent('p')+'.'+QuoteIdent('proname')+', '+QuoteIdent('p')+'.'+QuoteIdent('proargtypes')+' '+ - 'FROM '+QuoteIdent('pg_catalog')+'.'+QuoteIdent('pg_namespace')+' AS '+QuoteIdent('n')+' '+ - 'JOIN '+QuoteIdent('pg_catalog')+'.'+QuoteIdent('pg_proc')+' AS '+QuoteIdent('p')+' ON '+QuoteIdent('p')+'.'+QuoteIdent('pronamespace')+' = '+QuoteIdent('n')+'.'+QuoteIdent('oid')+' '+ - 'WHERE '+QuoteIdent('n')+'.'+QuoteIdent('nspname')+'='+EscapeString(db) - ); - except - on E:EDbError do; - end; - if Assigned(Results) then begin - while not Results.Eof do begin - obj := TDBObject.Create(Self); - Cache.Add(obj); - obj.Name := Results.Col('proname'); + tp := Results.Col('object_kind', True); + if tp = 'r' then + obj.NodeType := lntTable + else if tp = 'v' then begin + obj.NodeType := lntView; + obj.IsMaterialized := False; + end + else if tp = 'm' then begin + obj.NodeType := lntView; + obj.IsMaterialized := True; + end + else if tp = 'f' then + obj.NodeType := lntFunction + else if tp = 'p' then + obj.NodeType := lntProcedure; obj.ArgTypes := Results.Col('proargtypes'); - obj.Database := db; - obj.NodeType := lntFunction; Results.Next; end; FreeAndNil(Results); @@ -7231,12 +7462,15 @@ procedure TSQLiteConnection.FetchDbObjects(db: String; var Cache: TDBObjectList) var obj: TDBObject; Results: TDBQuery; + TypeS: String; + UnionRowCount: TStringList; begin // Tables, views and procedures Results := nil; try Results := GetResults('SELECT * FROM '+QuoteIdent(db)+'.sqlite_master '+ - 'WHERE type IN('+EscapeString('table')+', '+EscapeString('view')+') AND name NOT LIKE '+EscapeString('sqlite_%')); + 'WHERE type IN('+EscapeString('table')+', '+EscapeString('view')+', '+EscapeString('trigger')+') '+ + 'AND name NOT LIKE '+EscapeString('sqlite_%')); except on E:EDbError do; end; @@ -7248,14 +7482,46 @@ procedure TSQLiteConnection.FetchDbObjects(db: String; var Cache: TDBObjectList) obj.Created := Now; obj.Updated := Now; obj.Database := db; - if Results.Col('type').ToLowerInvariant = 'view' then begin + TypeS := Results.Col('type').ToLowerInvariant; + if TypeS = 'view' then begin obj.NodeType := lntView; obj.FCreateCode := Results.Col('sql'); + end else if TypeS = 'trigger' then begin + obj.NodeType := lntTrigger; + obj.FCreateCode := Results.Col('sql'); end else obj.NodeType := lntTable; Results.Next; end; FreeAndNil(Results); + + if FParameters.FullTableStatus then begin + UnionRowCount := TStringList.Create; + for obj in Cache do begin + if obj.NodeType <> lntTable then + Continue; + UnionRowCount.Add('SELECT '+EscapeString(obj.Name)+', COUNT(*) FROM '+QuoteIdent(obj.Database)+'.'+QuoteIdent(obj.Name)); + end; + if UnionRowCount.Count > 0 then + try + Results := GetResults(Implode(' UNION ', UnionRowCount)); + while not Results.Eof do begin + for obj in Cache do begin + if (obj.NodeType = lntTable) and (obj.Name = Results.Col(0)) then begin + obj.Rows := StrToInt64Def(Results.Col(1), -1); + obj.RowsAreExact := True; + break; + end; + end; + Results.Next; + end; + FreeAndNil(Results); + except + on E:EDbError do + Log(lcError, 'Full table status with row count not available in this database'); + end; + UnionRowCount.Free; + end; end; end; @@ -7375,37 +7641,45 @@ procedure TInterbaseConnection.FetchDbObjects(db: String; var Cache: TDBObjectLi end; -function TDBConnection.GetKeyColumns(Columns: TTableColumnList; Keys: TTableKeyList): TStringList; +function TDBConnection.GetKeyColumns(Columns: TTableColumnList; Keys: TTableKeyList): TTableColumnList; var - i: Integer; AllowsNull: Boolean; Key: TTableKey; Col: TTableColumn; + ColName: String; begin - Result := TStringList.Create; + Result := TTableColumnList.Create; // Find best key for updates // 1. round: find a primary key for Key in Keys do begin - if Key.IndexType = TTableKey.PRIMARY then - Result.Assign(Key.Columns); + if Key.IsPrimary then + begin + for ColName in Key.Columns do begin + Col := Columns.FindByName(ColName); + if Assigned(Col) then + Result.Add(Col); + end; + end; end; if Result.Count = 0 then begin // no primary key available -> 2. round: find a unique key for Key in Keys do begin - if Key.IndexType = TTableKey.UNIQUE then begin + if Key.IsUnique then begin // We found a UNIQUE key - better than nothing. Check if one of the key // columns allows NULLs which makes it dangerous to use in UPDATES + DELETES. AllowsNull := False; - for i:=0 to Key.Columns.Count-1 do begin - for Col in Columns do begin - if Col.Name = Key.Columns[i] then - AllowsNull := Col.AllowNull; - if AllowsNull then break; - end; - if AllowsNull then break; + for ColName in Key.Columns do begin + Col := Columns.FindByName(ColName); + AllowsNull := Assigned(Col) and Col.AllowNull; + if AllowsNull then + break; // Unusable, don't use this key end; if not AllowsNull then begin - Result.Assign(Key.Columns); + for ColName in Key.Columns do begin + Col := Columns.FindByName(ColName); + if Assigned(Col) then + Result.Add(Col); + end; break; end; end; @@ -7548,7 +7822,7 @@ procedure TDBConnection.ParseViewStructure(CreateCode: String; DBObj: TDBObject; CheckOption := Trim(rx.Match[11]); SelectCode := rx.Match[9]; end else - raise Exception.CreateFmt(_('Regular expression did not match the VIEW code in %s: %s'), ['ParseViewStructure()', CRLF+CRLF+CreateCode]); + Log(lcError, f_('Regular expression did not match the VIEW code in %s: %s', ['ParseViewStructure()', CRLF+CRLF+CreateCode])); rx.Free; end; @@ -7623,7 +7897,7 @@ procedure TDBConnection.ParseRoutineStructure(Obj: TDBObject; Parameters: TRouti // | SQL SECURITY { DEFINER | INVOKER } // | COMMENT 'string' rx.Expression := '^\s*('+ - 'RETURNS\s+(\S+(\s+UNSIGNED)?(\s+CHARSET\s+\S+)?(\s+COLLATE\s\S+)?)|'+ + 'RETURNS\s+((\S+\([^\)]+\)|\S+)(\s+UNSIGNED)?(\s+CHARSET\s+\S+)?(\s+COLLATE\s\S+)?)|'+ // MySQL function characteristics - see http://dev.mysql.com/doc/refman/5.1/de/create-procedure.html 'LANGUAGE\s+SQL|'+ '(NOT\s+)?DETERMINISTIC|'+ @@ -7651,7 +7925,7 @@ procedure TDBConnection.ParseRoutineStructure(Obj: TDBObject; Parameters: TRouti else if (Pos('CONTAINS SQL', Match) = 1) or (Pos('NO SQL', Match) = 1) or (Pos('READS SQL DATA', Match) = 1) or (Pos('MODIFIES SQL DATA', Match) = 1) then Obj.DataAccess := rx.Match[1] else if Pos('SQL SECURITY', Match) = 1 then - Obj.Security := rx.Match[7]; + Obj.Security := rx.Match[8]; Delete(Body, 1, rx.MatchLen[0]); @@ -7873,6 +8147,21 @@ destructor TInterbaseQuery.Destroy; end; +procedure TDBQuery.LogMetaInfo(NumResult: Integer); +var + MetaInfo: String; +begin + // Debug log output after DBQuery.Execute with result + MetaInfo := 'Result #'+IntToStr(NumResult)+' fetched in '; + if Connection.LastQueryDuration < 60*1000 then + MetaInfo := MetaInfo + FormatNumber(Connection.LastQueryDuration/1000, 3) +' ' + _('sec.') + else + MetaInfo := MetaInfo + FormatTimeNumber(Connection.LastQueryDuration/1000, True); + if Connection.LastQueryNetworkDuration > 0 then + MetaInfo := MetaInfo + ' (+ '+FormatNumber(Connection.LastQueryNetworkDuration/1000, 3) +' ' + _('sec.') + ' ' + _('network') + ')'; + Connection.Log(lcDebug, MetaInfo); +end; + procedure TMySQLQuery.Execute(AddResult: Boolean=False; UseRawResult: Integer=-1); var i, j, NumFields, NumResults: Integer; @@ -7905,7 +8194,7 @@ procedure TMySQLQuery.Execute(AddResult: Boolean=False; UseRawResult: Integer=-1 FEditingPrepared := False; end; if LastResult <> nil then begin - Connection.Log(lcDebug, 'Result #'+IntToStr(NumResults)+' fetched.'); + LogMetaInfo(NumResults); SetLength(FResultList, NumResults); FResultList[NumResults-1] := LastResult; FRecordCount := FRecordCount + LastResult.row_count; @@ -7997,7 +8286,7 @@ procedure TAdoDBQuery.Execute(AddResult: Boolean=False; UseRawResult: Integer=-1 FEditingPrepared := False; end; if LastResult <> nil then begin - Connection.Log(lcDebug, 'Result #'+IntToStr(NumResults)+' fetched.'); + LogMetaInfo(NumResults); SetLength(FResultList, NumResults); FResultList[NumResults-1] := LastResult; FRecordCount := FRecordCount + LastResult.RecordCount; @@ -8109,7 +8398,7 @@ procedure TPGQuery.Execute(AddResult: Boolean=False; UseRawResult: Integer=-1); FEditingPrepared := False; end; if LastResult <> nil then begin - Connection.Log(lcDebug, 'Result #'+IntToStr(NumResults)+' fetched.'); + LogMetaInfo(NumResults); SetLength(FResultList, NumResults); FResultList[NumResults-1] := LastResult; FRecordCount := FRecordCount + FConnection.Lib.PQntuples(LastResult); @@ -8171,7 +8460,7 @@ procedure TSQLiteQuery.Execute(AddResult: Boolean=False; UseRawResult: Integer=- FEditingPrepared := False; end; if LastResult <> nil then begin - Connection.Log(lcDebug, 'Result #'+IntToStr(NumResults)+' fetched.'); + LogMetaInfo(NumResults); SetLength(FResultList, NumResults); FResultList[NumResults-1] := LastResult; FRecordCount := FRecordCount + LastResult.Count; @@ -8254,7 +8543,7 @@ procedure TInterbaseQuery.Execute(AddResult: Boolean; UseRawResult: Integer); FEditingPrepared := False; end; if LastResult <> nil then begin - Connection.Log(lcDebug, 'Result #'+IntToStr(NumResults)+' fetched.'); + LogMetaInfo(NumResults); SetLength(FResultList, NumResults); FResultList[NumResults-1] := LastResult; FRecordCount := FRecordCount + LastResult.RecordCount; @@ -8646,6 +8935,12 @@ function TDBQuery.ColumnExists(Column: Integer): Boolean; end; +function TDBQuery.ColumnExists(ColumnName: String): Boolean; +begin + Result := FConnection.Active and ColumnNames.Contains(ColumnName); +end; + + function TDBQuery.GetColBinData(Column: Integer; var baData: TBytes): Boolean; begin Raise EDbError.Create(SNotImplemented); @@ -8718,8 +9013,9 @@ function TMySQLQuery.Col(Column: Integer; IgnoreErrors: Boolean=False): String; end; end; - end else if not IgnoreErrors then - Raise EDbError.CreateFmt(_(MsgInvalidColumn), [Column, ColumnCount, RecordCount]); + end + else + Result := TextInvalidColumn; end; @@ -8741,15 +9037,16 @@ function TAdoDBQuery.Col(Column: Integer; IgnoreErrors: Boolean=False): String; except Result := String(FCurrentResults.Fields[Column].AsAnsiString); end; - if Datatype(Column).Index = dbdtBit then begin - if UpperCase(Result) = 'TRUE' then - Result := '1' - else - Result := '0'; - end end; - end else if not IgnoreErrors then - Raise EDbError.CreateFmt(_(MsgInvalidColumn), [Column, ColumnCount, RecordCount]); + if Datatype(Column).Index = dbdtBit then begin + if (UpperCase(Result) = 'TRUE') or (Result = '1') then + Result := '1' + else + Result := '0'; + end + end + else + Result := TextInvalidColumn; end; @@ -8764,13 +9061,14 @@ function TPGQuery.Col(Column: Integer; IgnoreErrors: Boolean=False): String; SetString(AnsiStr, FConnection.Lib.PQgetvalue(FCurrentResults, FRecNoLocal, Column), FColumnLengths[Column]); if Datatype(Column).Category in [dtcBinary, dtcSpatial] then Result := String(AnsiStr) - else if Datatype(Column).Index = dbdtBool then - if AnsiStr='t' then Result := 'true' else Result := 'false' + else if (Datatype(Column).Index = dbdtBool) and (Length(AnsiStr) > 0) then + Result := IfThen(AnsiStr='t', 'true', 'false') else Result := Connection.DecodeAPIString(AnsiStr); end; - end else if not IgnoreErrors then - Raise EDbError.CreateFmt(_(MsgInvalidColumn), [Column, ColumnCount, RecordCount]); + end + else + Result := TextInvalidColumn; end; @@ -8782,8 +9080,9 @@ function TSQLiteQuery.Col(Column: Integer; IgnoreErrors: Boolean=False): String; end else begin Result := FCurrentResults[FRecNoLocal][Column].OldText; end; - end else if not IgnoreErrors then - Raise EDbError.CreateFmt(_(MsgInvalidColumn), [Column, ColumnCount, RecordCount]); + end + else + Result := TextInvalidColumn; end; @@ -8795,8 +9094,9 @@ function TInterbaseQuery.Col(Column: Integer; IgnoreErrors: Boolean): String; end else begin Result := FCurrentResults.Fields[Column].AsString; end; - end else if not IgnoreErrors then - Raise EDbError.CreateFmt(_(MsgInvalidColumn), [Column, ColumnCount, RecordCount]); + end + else + Result := TextInvalidColumn; end; @@ -8809,8 +9109,8 @@ function TDBQuery.Col(ColumnName: String; IgnoreErrors: Boolean=False): String; idx := ColumnNames.IndexOf(ColumnName); if idx > -1 then Result := Col(idx) - else if not IgnoreErrors then - Raise EDbError.CreateFmt(_('Column "%s" not available.'), [ColumnName]); + else + Result := TextInvalidColumn; end; @@ -8827,57 +9127,12 @@ function TDBQuery.HexValue(Column: Integer; IgnoreErrors: Boolean=False): String // Return a binary column value as hex AnsiString if FConnection.Parameters.IsAnyMysql then begin GetColBinData(Column, baData); - Result := HexValue(baData); + Result := FConnection.EscapeBin(baData); end else - Result := HexValue(Col(Column, IgnoreErrors)); + Result := FConnection.EscapeBin(Col(Column, IgnoreErrors)); end; -function TDBQuery.HexValue(BinValue: String): String; -var - BinLen: Integer; - Ansi: AnsiString; -begin - // Return a binary value as hex AnsiString - Ansi := AnsiString(BinValue); - BinLen := Length(Ansi); - if BinLen = 0 then begin - Result := Connection.EscapeString(''); - end else begin - if FConnection.IsHex(BinValue) then begin - Result := BinValue; // Already hex encoded - end else begin - SetLength(Result, BinLen*2); - BinToHex(PAnsiChar(Ansi), PChar(Result), BinLen); - Result := '0x' + Result; - end; - if AppSettings.ReadBool(asLowercaseHex) then - Result := Result.ToLowerInvariant; - end; -end; - -function TDBQuery.HexValue(var ByteData: TBytes): String; -var - BinLen: Integer; - Ansi: AnsiString; -begin - BinLen := Length(ByteData); - SetString(Ansi, PAnsiChar(ByteData), BinLen); - if BinLen = 0 then begin - Result := Connection.EscapeString(''); - end else begin - if FConnection.IsHex(String(Ansi)) then begin - Result := String(Ansi); // Already hex encoded - end else begin - SetLength(Result, BinLen*2); - BinToHex(PAnsiChar(Ansi), PChar(Result), BinLen); - Result := '0x' + Result; - end; - if AppSettings.ReadBool(asLowercaseHex) then - Result := Result.ToLowerInvariant; - end; -end; - function TDBQuery.DataType(Column: Integer): TDBDataType; var Col: TTableColumn; @@ -8919,16 +9174,36 @@ function TDBQuery.MaxLength(Column: Integer): Int64; function TDBQuery.ValueList(Column: Integer): TStringList; var ColAttr: TTableColumn; + i: Integer; begin Result := TStringList.Create; - Result.QuoteChar := ''''; - Result.Delimiter := ','; ColAttr := ColAttributes(Column); if Assigned(ColAttr) then case ColAttr.DataType.Index of - dbdtEnum, dbdtSet: - Result.DelimitedText := ColAttr.LengthSet; - dbdtBool: + + dbdtEnum, dbdtSet: begin + // Lool up PostgreSQL enum labels in prefetched list + i := FConnection.NamedEnums.IndexOfName(ColAttr.LengthSet); + if i > -1 then begin + Result.Delimiter := '|'; + Result.DelimitedText := FConnection.NamedEnums.ValueFromIndex[i]; + end + else begin + // .. or in MySQL Length/Set + Result.QuoteChar := ''''; + Result.Delimiter := ','; + Result.DelimitedText := ColAttr.LengthSet; + end; + // In any case, take care for escaped ENUM definitions, see issue #799 + for i:=0 to Result.Count-1 do begin + Result[i] := FConnection.UnescapeString(Result[i]); + end; + end; + + dbdtBool: begin + Result.Delimiter := ','; Result.DelimitedText := 'true,false'; + end; + end; end; @@ -8938,8 +9213,9 @@ function TDBQuery.ColAttributes(Column: Integer): TTableColumn; i: Integer; begin Result := nil; - if (Column < 0) or (Column >= FColumnOrgNames.Count) then - raise EDbError.CreateFmt(_('Column #%s not available.'), [IntToStr(Column)]); + if (Column < 0) or (Column >= FColumnOrgNames.Count) then begin + // Just return nil + end; if FColumns <> nil then begin for i:=0 to FColumns.Count-1 do begin if FColumns[i].Name = FColumnOrgNames[Column] then begin @@ -9243,6 +9519,7 @@ procedure TDBQuery.DeleteRow; var sql: String; IsVirtual: Boolean; + TempRowsAffected: Int64; begin // Delete current row from result PrepareEditing; @@ -9250,8 +9527,10 @@ procedure TDBQuery.DeleteRow; if not IsVirtual then begin sql := GridQuery('DELETE', 'FROM ' + QuotedDbAndTableName + ' WHERE ' + GetWhereClause); Connection.Query(sql); - if Connection.RowsAffected = 0 then - raise EDbError.Create(FormatNumber(Connection.RowsAffected)+' rows deleted when that should have been 1.'); + TempRowsAffected := Connection.RowsAffected; + Connection.ShowWarnings; + if TempRowsAffected = 0 then + raise EDbError.Create(FormatNumber(TempRowsAffected)+' rows deleted when that should have been 1.'); end; if Assigned(FCurrentUpdateRow) then begin FUpdateData.Remove(FCurrentUpdateRow); @@ -9277,12 +9556,23 @@ function TDBQuery.InsertRow: Int64; Row.Add(c); c.OldText := ''; c.OldIsFunction := False; - c.OldIsNull := False; + c.OldIsNull := True; ColAttr := ColAttributes(i); if Assigned(ColAttr) then begin - c.OldIsNull := ColAttr.DefaultType in [cdtNull, cdtAutoInc, cdtExpression]; - if ColAttr.DefaultType in [cdtText] then - c.OldText := FConnection.UnescapeString(ColAttr.DefaultText); + case ColAttr.DefaultType of + cdtText: begin + c.OldText := FConnection.UnescapeString(ColAttr.DefaultText); + c.OldIsNull := False; + end; + cdtExpression: begin + // Overtake expression, if it's a simple integer + if ColAttr.DefaultText = MakeInt(ColAttr.DefaultText).ToString then begin + c.OldText := ColAttr.DefaultText; + c.OldIsNull := False; + end; + end; + end; + end; c.NewText := c.OldText; c.NewIsFunction := c.OldIsFunction; @@ -9368,6 +9658,7 @@ function TDBQuery.EnsureFullRow(Refresh: Boolean): Boolean; sql := sql + ' FROM '+QuotedDbAndTableName+' WHERE '+GetWhereClause; sql := GridQuery('SELECT', sql); Data := Connection.GetResults(sql); + Connection.ShowWarnings; Result := Data.RecordCount = 1; if Result then begin if not Assigned(FCurrentUpdateRow) then @@ -9379,6 +9670,7 @@ function TDBQuery.EnsureFullRow(Refresh: Boolean): Boolean; FCurrentUpdateRow[i].NewIsNull := FCurrentUpdateRow[i].OldIsNull; FCurrentUpdateRow[i].OldIsFunction := False; FCurrentUpdateRow[i].NewIsFunction := FCurrentUpdateRow[i].OldIsFunction; + FColumnLengths[i] := Length(FCurrentUpdateRow[i].NewText); end; Data.Free; end; @@ -9434,6 +9726,7 @@ function TDBQuery.HasFullData: Boolean; function TDBQuery.SaveModifications: Boolean; var i: Integer; + TempRowsAffected: Int64; Row: TGridRow; Cell: TGridValue; sqlUpdate, sqlInsertColumns, sqlInsertValues, Val: String; @@ -9466,21 +9759,14 @@ function TDBQuery.SaveModifications: Boolean; else if Cell.NewIsFunction then Val := Cell.NewText else case Datatype(i).Category of - dtcInteger, dtcReal: begin + dtcInteger, dtcReal: Val := Connection.EscapeString(Cell.NewText, Datatype(i)); - if (Datatype(i).Index = dbdtBit) and FConnection.Parameters.IsAnyMySQL then - Val := 'b' + Val; - end; dtcBinary, dtcSpatial: - Val := HexValue(Cell.NewText); - else begin - if Datatype(i).Index in [dbdtNchar, dbdtNvarchar, dbdtNtext] then - Val := 'N' + Connection.EscapeString(Cell.NewText) - else if Datatype(i).Category = dtcTemporal then - Val := Connection.EscapeString(Connection.GetDateTimeValue(Cell.NewText, Datatype(i).Index)) - else - Val := Connection.EscapeString(Cell.NewText); - end; + Val := FConnection.EscapeBin(Cell.NewText); + dtcTemporal: + Val := Connection.EscapeString(Connection.GetDateTimeValue(Cell.NewText, Datatype(i).Index)) + else + Val := Connection.EscapeString(Cell.NewText, Datatype(i)); end; sqlUpdate := sqlUpdate + Connection.QuoteIdent(FColumnOrgNames[i]) + '=' + Val; sqlInsertColumns := sqlInsertColumns + Connection.QuoteIdent(FColumnOrgNames[i]); @@ -9491,12 +9777,13 @@ function TDBQuery.SaveModifications: Boolean; if RowModified then try if Row.Inserted then begin Connection.Query('INSERT INTO '+QuotedDbAndTableName+' ('+sqlInsertColumns+') VALUES ('+sqlInsertValues+')'); + Connection.ShowWarnings; for i:=0 to ColumnCount-1 do begin ColAttr := ColAttributes(i); if Assigned(ColAttr) and (ColAttr.DefaultType = cdtAutoInc) then begin Row[i].NewText := UnformatNumber(Row[i].NewText); if Row[i].NewText = '0' then - Row[i].NewText := Connection.GetVar('SELECT ' + Connection.GetSQLSpecifity(spFuncLastAutoIncNumber)); + Row[i].NewText := Connection.GetVar('SELECT ' + Connection.SqlProvider.GetSql(qFuncLastAutoIncNumber)); Row[i].NewIsNull := False; break; end; @@ -9505,8 +9792,10 @@ function TDBQuery.SaveModifications: Boolean; sqlUpdate := QuotedDbAndTableName+' SET '+sqlUpdate+' WHERE '+GetWhereClause; sqlUpdate := GridQuery('UPDATE', sqlUpdate); Connection.Query(sqlUpdate); - if Connection.RowsAffected = 0 then begin - raise EDbError.Create(FormatNumber(Connection.RowsAffected)+' rows updated when that should have been 1.'); + TempRowsAffected := Connection.RowsAffected; + Connection.ShowWarnings; + if TempRowsAffected = 0 then begin + raise EDbError.Create(FormatNumber(TempRowsAffected)+' rows updated when that should have been 1.'); Result := False; end; end; @@ -9678,16 +9967,12 @@ function TMySQLQuery.TableName(Column: Integer): String; Objects: TDBObjectList; Obj: TDBObject; begin + Result := ''; Field := FConnection.Lib.mysql_fetch_field_direct(FCurrentResults, Column); - {Connection.Log(lcDebug, FColumnNames[Column]+':'+ - ' org_table:'+Field.org_table+ - ' table:'+Field.table+ - ' table^=org_table^:'+(Field.table^ = Field.org_table^).ToInteger.ToString+ - ' table=org_table:'+(Field.table = Field.org_table).ToInteger.ToString - );} FieldDb := FConnection.DecodeAPIString(Field.db); FieldTable := FConnection.DecodeAPIString(Field.table); FieldOrgTable := FConnection.DecodeAPIString(Field.org_table); + // Connection.Log(lcInfo, FColumnNames[Column]+': org_table:'+FieldOrgTable+' table:'+FieldTable); if FieldTable <> FieldOrgTable then begin // Probably a VIEW, in which case we rely on the first column's table name. @@ -9701,10 +9986,13 @@ function TMySQLQuery.TableName(Column: Integer): String; end; end; end; - end else begin + end; + + if Result.IsEmpty then begin // Normal table column // Note: this is empty on data tab TEXT columns with LEFT(..) clause Result := FieldOrgTable; + StripNewLines(Result); end; end; @@ -9716,18 +10004,21 @@ function TAdoDBQuery.TableName(Column: Integer): String; function TPGQuery.TableName(Column: Integer): String; var - FieldTypeOID: POid; + TableOid: POid; begin // Get table name from a result set // "123::regclass" results are quoted if they contain special characters - Result := EmptyStr; - FieldTypeOID := FConnection.Lib.PQftable(FCurrentResults, Column); - if not FConnection.RegClasses.ContainsKey(FieldTypeOID) then begin - Result := FConnection.GetVar('SELECT '+IntToStr(FieldTypeOID)+'::regclass'); - Result := FConnection.DeQuoteIdent(Result); - FConnection.RegClasses.Add(FieldTypeOID, Result); + TableOid := FConnection.Lib.PQftable(FCurrentResults, Column); + if TableOid = InvalidOid then begin + // 0 => not a simple reference to a table column, e.g. on SUBSTRING(col, 1, 256) + Result := EmptyStr; + end + else if FConnection.RegClasses.ContainsKey(TableOid) then begin + FConnection.RegClasses.TryGetValue(TableOid, Result); end else begin - FConnection.RegClasses.TryGetValue(FieldTypeOID, Result); + Result := FConnection.GetVar('SELECT '+IntToStr(TableOid)+'::regclass'); + Result := FConnection.DeQuoteIdent(Result); + FConnection.RegClasses.Add(TableOid, Result); end; end; @@ -9758,7 +10049,14 @@ function TDBQuery.QuotedDbAndTableName: String; end; -function TDBQuery.GetKeyColumns: TStringList; +function TDBQuery.ResultName: String; +begin + // Return name of query defined in a comment above the actual query + Result := RegExprGetMatch('--\s+name\:\s*([^\r\n]+)', FSQL, 1, False, True); + Result := Trim(Result); +end; + +function TDBQuery.GetKeyColumns: TTableColumnList; var i: Integer; begin @@ -9768,7 +10066,7 @@ function TDBQuery.GetKeyColumns: TStringList; if Result.Count = 0 then begin // No good key found. Just expect all columns to be present. for i:=0 to FColumns.Count-1 do - Result.Add(FColumns[i].Name); + Result.Add(FColumns[i]); end; end; @@ -9776,14 +10074,14 @@ function TDBQuery.GetKeyColumns: TStringList; procedure TDBQuery.CheckEditable; var i: Integer; - KeyCols: TStringList; + KeyCols: TTableColumnList; begin KeyCols := GetKeyColumns; if KeyCols.Count = 0 then raise EDbError.Create(_(MSG_NOGRIDEDITING)); // All column names must be present in order to send valid INSERT/UPDATE/DELETE queries for i:=0 to KeyCols.Count-1 do begin - if FColumnOrgNames.IndexOf(KeyCols[i]) = -1 then + if FColumnOrgNames.IndexOf(KeyCols[i].Name) = -1 then raise EDbError.Create(_(MSG_NOGRIDEDITING)); end; for i:=0 to FColumnOrgNames.Count-1 do begin @@ -9799,7 +10097,7 @@ function TDBQuery.IsEditable: Boolean; Result := True; except on E:EDbError do begin - FConnection.Log(lcDebug, E.Message); + FConnection.Log(lcInfo, E.Message); Result := False; end; end; @@ -9809,7 +10107,7 @@ function TDBQuery.IsEditable: Boolean; function TDBQuery.GetWhereClause: String; var i, j: Integer; - NeededCols: TStringList; + NeededCols: TTableColumnList; ColVal: String; ColIsNull: Boolean; begin @@ -9818,16 +10116,13 @@ function TDBQuery.GetWhereClause: String; Result := ''; for i:=0 to NeededCols.Count-1 do begin - j := FColumnOrgNames.IndexOf(NeededCols[i]); + j := FColumnOrgNames.IndexOf(NeededCols[i].Name); if j = -1 then - raise EDbError.CreateFmt(_('Cannot compose WHERE clause - column missing: %s'), [NeededCols[i]]); + raise EDbError.CreateFmt(_('Cannot compose WHERE clause - column missing: %s'), [NeededCols[i].Name]); if Result <> '' then Result := Result + ' AND'; - - Result := Result + ' ' + Connection.QuoteIdent(FColumnOrgNames[j]); - if (DataType(j).Index = dbdtJson) and (Self is TPGQuery) then begin - Result := Result + '::text'; - end; + // See issue #769 and #2031 for why we need CastAsText + Result := Result + ' ' + NeededCols[i].CastAsText; if Modified(j) then begin ColVal := FCurrentUpdateRow[j].OldText; @@ -9843,7 +10138,7 @@ function TDBQuery.GetWhereClause: String; case DataType(j).Category of dtcInteger, dtcReal: begin if DataType(j).Index = dbdtBit then - Result := Result + '=b' + Connection.EscapeString(ColVal) + Result := Result + '=' + Connection.EscapeString(ColVal, DataType(j)) else begin // Guess (!) the default value silently inserted by the server. This is likely // to be incomplete in cases where a UNIQUE key allows NULL here @@ -9855,16 +10150,10 @@ function TDBQuery.GetWhereClause: String; dtcTemporal: Result := Result + '=' + Connection.EscapeString(Connection.GetDateTimeValue(ColVal, DataType(j).Index)); dtcBinary, dtcSpatial: - Result := Result + '=' + HexValue(ColVal); + Result := Result + '=' + FConnection.EscapeBin(ColVal); else begin // Any other data type goes here, including text: - case DataType(j).Index of - // Support international characters with N-prefix on MSSQL, see #1115: - dbdtNchar, dbdtNvarchar, dbdtNtext: - Result := Result + '=N' + Connection.EscapeString(ColVal); - else - Result := Result + '=' + Connection.EscapeString(ColVal); - end; + Result := Result + '=' + Connection.EscapeString(ColVal, DataType(j)); end; end; end; @@ -9874,7 +10163,7 @@ function TDBQuery.GetWhereClause: String; function TDBQuery.GridQuery(QueryType, QueryBody: String): String; var - KeyColumns: TStringList; + KeyColumns: TTableColumnList; begin // Return automatic grid UPDATE/DELETE/SELECT, and apply LIMIT clause if no good key is present KeyColumns := Connection.GetKeyColumns(FColumns, FKeys); @@ -9948,33 +10237,51 @@ function TDBObjectDropComparer.Compare(const Left, Right: TDBObject): Integer; constructor TDBObject.Create(OwnerConnection: TDBConnection); begin - NodeType := lntNone; - GroupType := lntNone; + // Take care, when adding properties here, add them in Assign() below as well Name := ''; - Database := ''; Schema := ''; - Rows := -1; - Size := -1; - Created := 0; - Updated := 0; + Database := ''; + Column := ''; Engine := ''; Comment := ''; - Version := -1; - AutoInc := -1; RowFormat := ''; + CreateOptions := ''; + Collation := ''; + Created := 0; + Updated := 0; + LastChecked := 0; + Rows := -1; + Size := -1; + Version := -1; AvgRowLen := -1; MaxDataLen := -1; IndexLen := -1; DataLen := -1; DataFree := -1; - LastChecked := 0; - Collation := ''; + AutoInc := -1; CheckSum := -1; - CreateOptions := ''; + Body := ''; + Definer := ''; + Returns := ''; + DataAccess := ''; + Security := ''; + ArgTypes := ''; + Deterministic := False; + RowsAreExact := False; + IsMaterialized := False; + NodeType := lntNone; + GroupType := lntNone; FCreateCode := ''; FCreateCodeLoaded := False; - RowsAreExact := False; + FWasSelected := False; FConnection := OwnerConnection; + FMap := TStringMap.Create; +end; + +destructor TDBObject.Destroy; +begin + FMap.Free; + inherited; end; @@ -9985,22 +10292,41 @@ procedure TDBObject.Assign(Source: TPersistent); if Source is TDBObject then begin s := Source as TDBObject; Name := s.Name; - Column := s.Column; - Collation := s.Collation; - Engine := s.Engine; Schema := s.Schema; Database := s.Database; - NodeType := s.NodeType; - GroupType := s.GroupType; + Column := s.Column; + Engine := s.Engine; + Comment := s.Comment; + RowFormat := s.RowFormat; + CreateOptions := s.CreateOptions; + Collation := s.Collation; Created := s.Created; Updated := s.Updated; - Comment := s.Comment; + LastChecked := s.LastChecked; Rows := s.Rows; - RowsAreExact := s.RowsAreExact; Size := s.Size; + Version := s.Version; + AvgRowLen := s.AvgRowLen; + MaxDataLen := s.MaxDataLen; + IndexLen := s.IndexLen; + DataLen := s.DataLen; + DataFree := s.DataFree; + AutoInc := s.AutoInc; + CheckSum := s.CheckSum; + Body := s.Body; + Definer := s.Definer; + Returns := s.Returns; + DataAccess := s.DataAccess; + Security := s.Security; ArgTypes := s.ArgTypes; + Deterministic := s.Deterministic; + RowsAreExact := s.RowsAreExact; + IsMaterialized := s.IsMaterialized; + NodeType := s.NodeType; + GroupType := s.GroupType; FCreateCode := s.FCreateCode; FCreateCodeLoaded := s.FCreateCodeLoaded; + FWasSelected := s.FWasSelected; end else inherited; end; @@ -10235,10 +10561,10 @@ function TDBObject.SchemaClauseIS(Prefix: String): String; if Schema <> '' then Result := Prefix+'_SCHEMA' + '=' + Connection.EscapeString(Schema) else - Result := Connection.GetSQLSpecifity(spISSchemaCol, [Prefix]) + '=' + Connection.EscapeString(Database); + Result := Connection.SqlProvider.GetSql(qISSchemaCol, [Prefix]) + '=' + Connection.EscapeString(Database); end; -function TDBObject.RowCount(Reload: Boolean; ForceExact: Bool=False): Int64; +function TDBObject.RowCount(Reload: Boolean; ForceExact: Boolean=False): Int64; begin if (Rows = -1) or Reload then begin Rows := Connection.GetRowCount(Self, ForceExact); @@ -10305,6 +10631,21 @@ function TDBObject.GetTableCheckConstraints: TCheckConstraintList; Result.Assign(CheckConstraintsInCache); end; +function TDBObject.AsStringMap: TStringMap; +begin + FMap.Clear; + FMap.Add('EscapedName', FConnection.EscapeString(Name)); + FMap.Add('EscapedSchema', FConnection.EscapeString(Schema)); + FMap.Add('EscapedDatabase', FConnection.EscapeString(Database)); + FMap.Add('EscapedDbSchemaName', FConnection.EscapeString(Database+'.'+Schema+'.'+Name)); + FMap.Add('QuotedDatabase', QuotedDatabase); + FMap.Add('QuotedName', QuotedName); + FMap.Add('QuotedDbAndTableName', QuotedDbAndTableName); + FMap.Add('ObjType', UpperCase(ObjType)); + Result := FMap; +end; + + { *** TTableColumn } @@ -10362,8 +10703,10 @@ constructor TTableColumn.Create(AOwner: TDBConnection; Serialized: String=''); GenerationExpression := FromSerialized('Expression', ''); Virtuality := FromSerialized('Virtuality', ''); Invisible := FromSerialized('Invisible', '0').ToInteger.ToBoolean; + SRID := FromSerialized('SRID', '0').ToInteger; NumVal := FromSerialized('Status', Integer(esUntouched).ToString); FStatus := TEditingStatus(NumVal.ToInteger); + Compressed := FromSerialized('Compressed', '0').ToInteger.ToBoolean; Attributes.Free; end; @@ -10398,6 +10741,8 @@ procedure TTableColumn.Assign(Source: TPersistent); GenerationExpression := s.GenerationExpression; Virtuality := s.Virtuality; Invisible := s.Invisible; + SRID := s.SRID; + Compressed := s.Compressed; FStatus := s.FStatus; end else inherited; @@ -10431,6 +10776,8 @@ function TTableColumn.Serialize: String; s.AddPair('GenerationExpression', GenerationExpression); s.AddPair('Virtuality', Virtuality); s.AddPair('Invisible', Invisible.ToInteger.ToString); + s.AddPair('SRID', SRID.ToString); + s.AddPair('Compressed', Compressed.ToInteger.ToString); s.AddPair('Status', Integer(FStatus).ToString); Result := Implode(DELIMITER, s); @@ -10452,6 +10799,7 @@ procedure TTableColumn.SetStatus(Value: TEditingStatus); function TTableColumn.SQLCode(OverrideCollation: String=''; Parts: TColumnParts=[cpAll]): String; var IsVirtual: Boolean; + QuoteCollation: Boolean; function InParts(Part: TColumnPart): Boolean; begin @@ -10466,32 +10814,45 @@ function TTableColumn.SQLCode(OverrideCollation: String=''; Parts: TColumnParts= end; if InParts(cpType) then begin - case FConnection.Parameters.NetTypeGroup of - ngPgSQL: begin - if DefaultType = cdtAutoInc then - Result := Result + 'SERIAL' - else - Result := Result + DataType.Name; + + if FConnection.Parameters.IsAnyPostgreSQL and (DefaultType = cdtAutoInc) then begin + Result := Result + 'SERIAL'; + end + else begin + + if (DataType.Index = dbdtEnum) and (FConnection.NamedEnums.IndexOfName(LengthSet) > -1) then begin + Result := Result + LengthSet; + end + else begin + Result := Result + DataType.Name; + if (LengthSet <> '') and DataType.HasLength then + Result := Result + '(' + LengthSet + ')'; end; - else Result := Result + DataType.Name; + + if (DataType.Category in [dtcInteger, dtcReal]) and Unsigned then + Result := Result + ' UNSIGNED'; + if (DataType.Category in [dtcInteger, dtcReal]) and ZeroFill then + Result := Result + ' ZEROFILL'; + if Compressed and FConnection.Parameters.IsMariaDB then + Result := Result + ' /*!100301 COMPRESSED*/'; end; - if (LengthSet <> '') and DataType.HasLength then - Result := Result + '(' + LengthSet + ')'; - if (DataType.Category in [dtcInteger, dtcReal]) and Unsigned then - Result := Result + ' UNSIGNED'; - if (DataType.Category in [dtcInteger, dtcReal]) and ZeroFill then - Result := Result + ' ZEROFILL'; Result := Result + ' '; // Add space after each part end; - if InParts(cpAllowNull) and (not IsVirtual) then begin + if InParts(cpAllowNull) and (not IsVirtual) and (not FConnection.Parameters.IsAnyMSSQL) then begin if not AllowNull then Result := Result + 'NOT NULL ' else if not FConnection.Parameters.IsAnyInterbase then Result := Result + 'NULL '; end; + // SRID for spatial columns supported since MySQL 8.0 + if InParts(cpSRID) and (DataType.Category = dtcSpatial) and FConnection.Has(frSrid) then begin + Result := Result + 'SRID ' + SRID.ToString + ' '; + end; + + if InParts(cpDefault) and (not IsVirtual) then begin if DefaultType <> cdtNothing then begin case DefaultType of @@ -10501,11 +10862,11 @@ function TTableColumn.SQLCode(OverrideCollation: String=''; Parts: TColumnParts= cdtAutoInc: begin case FConnection.Parameters.NetTypeGroup of ngPgSQL:; - else Result := Result + AutoIncName; + else Result := Result + FConnection.SqlProvider.GetSql(qAutoInc); end; end; cdtExpression: begin - if FConnection.Parameters.IsMySQL(True) and (FConnection.ServerVersionInt >= 80013) then + if FConnection.Has(frColumnDefaultParentheses) then Result := Result + 'DEFAULT ('+DefaultText+')' else Result := Result + 'DEFAULT '+DefaultText; @@ -10528,6 +10889,10 @@ function TTableColumn.SQLCode(OverrideCollation: String=''; Parts: TColumnParts= Result := Result + 'AS ('+GenerationExpression+') ' + Virtuality + ' '; end; + if InParts(cpInvisible) and Invisible and FConnection.Has(frInvisibleColumns) then begin + Result := Result + 'INVISIBLE '; + end; + if InParts(cpComment) then begin if (Comment <> '') and FConnection.Parameters.IsAnyMySQL then Result := Result + 'COMMENT ' + FConnection.EscapeString(Comment) + ' '; @@ -10536,10 +10901,11 @@ function TTableColumn.SQLCode(OverrideCollation: String=''; Parts: TColumnParts= if InParts(cpCollation) and (not IsVirtual) and (DataType.Index <> dbdtJson) then begin if Collation <> '' then begin Result := Result + 'COLLATE '; + QuoteCollation := not FConnection.Parameters.IsAnyMSSQL; if OverrideCollation <> '' then - Result := Result + FConnection.EscapeString(OverrideCollation) + ' ' + Result := Result + IfThen(QuoteCollation, FConnection.EscapeString(OverrideCollation), OverrideCollation) + ' ' else - Result := Result + FConnection.EscapeString(Collation) + ' '; + Result := Result + IfThen(QuoteCollation, FConnection.EscapeString(Collation), Collation) + ' '; end; end; @@ -10562,7 +10928,9 @@ procedure TTableColumn.ParseDatatype(Source: String); var InLiteral: Boolean; ParenthLeft, i: Integer; + OrgSource: String; begin + OrgSource := Source; DataType := Connection.GetDatatypeByName(Source, True); // Length / Set // Various datatypes, e.g. BLOBs, don't have any length property @@ -10575,14 +10943,20 @@ procedure TTableColumn.ParseDatatype(Source: String); if Source[i] = '''' then InLiteral := not InLiteral; end; - LengthSet := Copy(Source, ParenthLeft+1, i-2); + LengthSet := Copy(Source, ParenthLeft+1, i-1-ParenthLeft); if LengthSet = DataType.DefaultSize.ToString then LengthSet := ''; end else begin LengthSet := ''; + if DataType.Index = dbdtEnum then begin + // Assign PostgreSQL enum type to LengthSet, so we can provide it in table editor + // Some enum types are wrapped in double quotes + LengthSet := OrgSource.Trim([FConnection.QuoteChar]); + end; end; Unsigned := ExecRegExpr('\bunsigned\b', Source.ToLowerInvariant); ZeroFill := ExecRegExpr('\bzerofill\b', Source.ToLowerInvariant); + Compressed := ExecRegExpr('\bcompressed\W', Source.ToLowerInvariant); end; @@ -10592,7 +10966,7 @@ function TTableColumn.CastAsText: String; Result := FConnection.QuoteIdent(Name); case FConnection.Parameters.NetTypeGroup of ngMySQL, ngSQLite: begin - if DataType.Index in [dbdtUnknown, dbdtDate, dbdtDatetime, dbdtTime, dbdtTimestamp] then + if DataType.Index in [dbdtUnknown, dbdtDate, dbdtDatetime, dbdtTime, dbdtTimestamp, dbdtJson, dbdtJsonB] then Result := 'CAST('+Result+' AS CHAR)'; end; ngMSSQL: begin @@ -10602,18 +10976,22 @@ function TTableColumn.CastAsText: String; Result := 'CAST('+Result+' AS NVARCHAR('+IntToStr(GRIDMAXDATA)+'))'; end; ngPgSQL: begin - if (DataType.Index = dbdtUnknown) or (DataType.Category = dtcBinary) then + // Cast most datatypes, including VARCHAR and TEXT, which may have an [] array attribute + if not (DataType.Category in [dtcInteger, dtcReal]) then Result := Result + '::text'; end; end; end; -function TTableColumn.AutoIncName: String; +function TTableColumn.FullDataType: String; begin - case FConnection.Parameters.NetTypeGroup of - ngPgSQL: Result := 'SERIAL'; - else Result := 'AUTO_INCREMENT'; + Result := DataType.Name; + if not LengthSet.IsEmpty then begin + if (DataType.Index = dbdtEnum) and (FConnection.NamedEnums.IndexOfName(LengthSet) > -1) then + Result := LengthSet + else + Result := Result + '(' + LengthSet + ')'; end; end; @@ -10630,6 +11008,45 @@ procedure TTableColumnList.Assign(Source: TTableColumnList); end; +function TTableColumnList.FindByName(const Value: String): TTableColumn; +var + Col: TTableColumn; +begin + Result := nil; + for Col in Self do begin + if Col.Name = Value then begin + Result := Col; + break; + end; + end; +end; + +function TTableColumnList.HasInvisibleColumns: Boolean; +var + Col: TTableColumn; +begin + Result := False; + for Col in Self do begin + if Col.Invisible then begin + Result := True; + Break; + end; + end; +end; + +function TTableColumnList.QuoteIdents: String; +var + Col: TTableColumn; + QuotedNames: TStringList; +begin + QuotedNames := TStringList.Create; + for Col in Self do begin + QuotedNames.Add(Col.Connection.QuoteIdent(Col.Name)); + end; + Result := Implode(', ', QuotedNames); + QuotedNames.Free; +end; + { *** TTableKey } @@ -10639,14 +11056,18 @@ constructor TTableKey.Create(AOwner: TDBConnection); FConnection := AOwner; Columns := TStringList.Create; SubParts := TStringList.Create; + Collations := TStringList.Create; Columns.OnChange := Modification; Subparts.OnChange := Modification; + Collations.OnChange := Modification; + Visible := True; end; destructor TTableKey.Destroy; begin FreeAndNil(Columns); FreeAndNil(SubParts); + FreeAndNil(Collations); inherited Destroy; end; @@ -10662,14 +11083,52 @@ procedure TTableKey.Assign(Source: TPersistent); OldIndexType := s.OldIndexType; Algorithm := s.Algorithm; Comment := s.Comment; + Visible := s.Visible; Columns.Assign(s.Columns); SubParts.Assign(s.SubParts); + Collations.Assign(s.Collations); Modified := s.Modified; Added := s.Added; end else inherited; end; +function TTableKey.IsPrimary: Boolean; +begin + Result := IndexType = PRIMARY; +end; + +function TTableKey.IsIndex: Boolean; +begin + Result := IndexType = KEY; +end; + +function TTableKey.IsUnique: Boolean; +begin + Result := IndexType = UNIQUE; +end; + +function TTableKey.IsFulltext: Boolean; +begin + Result := IndexType = FULLTEXT; +end; + +function TTableKey.IsSpatial: Boolean; +begin + Result := IndexType = SPATIAL; +end; + +function TTableKey.IsVector: Boolean; +begin + Result := IndexType = VECTOR; +end; + +function TTableKey.IsExpression(KeyPart: Integer): Boolean; +begin + Result := Columns[KeyPart].StartsWith('('); +end; + + procedure TTableKey.Modification(Sender: TObject); begin if not Added then @@ -10679,15 +11138,26 @@ procedure TTableKey.Modification(Sender: TObject); function TTableKey.GetImageIndex: Integer; begin // Detect key icon index for specified index - if IndexType = TTableKey.PRIMARY then Result := ICONINDEX_PRIMARYKEY - else if IndexType = TTableKey.KEY then Result := ICONINDEX_INDEXKEY - else if IndexType = TTableKey.UNIQUE then Result := ICONINDEX_UNIQUEKEY - else if IndexType = TTableKey.FULLTEXT then Result := ICONINDEX_FULLTEXTKEY - else if IndexType = TTableKey.SPATIAL then Result := ICONINDEX_SPATIALKEY + if IsPrimary then Result := ICONINDEX_PRIMARYKEY + else if IsIndex then Result := ICONINDEX_INDEXKEY + else if IsUnique then Result := ICONINDEX_UNIQUEKEY + else if IsFulltext then Result := ICONINDEX_FULLTEXTKEY + else if IsSpatial then Result := ICONINDEX_SPATIALKEY + else if IsVector then Result := ICONINDEX_VECTORKEY else Result := -1; end; -function TTableKey.SQLCode: String; +function TTableKey.GetInsideCreateCode: Boolean; +begin + case FConnection.Parameters.NetTypeGroup of + ngMySQL: Result := True; + ngSQLite: Result := IsPrimary; + ngPgSQL: Result := IsPrimary or IsUnique; + else Result := True; + end; +end; + +function TTableKey.SQLCode(TableName: String=''): String; var i: Integer; begin @@ -10695,30 +11165,61 @@ function TTableKey.SQLCode: String; // Supress SQL error trying index creation with 0 column if Columns.Count = 0 then Exit; - if IndexType = TTableKey.PRIMARY then - Result := Result + 'PRIMARY KEY ' + if InsideCreateCode then begin + if IsPrimary then + Result := Result + 'PRIMARY KEY ' + else begin + if FConnection.Parameters.IsAnyPostgreSQL then begin + Result := Result + IndexType + ' '; + end + else begin + if not IsIndex then + Result := Result + IndexType + ' '; + Result := Result + 'INDEX ' + FConnection.QuoteIdent(Name) + ' '; + end; + end; + Result := Result + '('; + for i:=0 to Columns.Count-1 do begin + if IsExpression(i) then + Result := Result + Columns[i] // Don't quote functional key part + else + Result := Result + FConnection.QuoteIdent(Columns[i]); + if (SubParts.Count > i) and (SubParts[i] <> '') then + Result := Result + '(' + SubParts[i] + ')'; + // Collation / sort order, see issue #1512 + if (Collations.Count > i) and (Collations[i].ToLower = 'd') then + Result := Result + ' DESC'; + Result := Result + ', '; + end; + if Columns.Count > 0 then + Delete(Result, Length(Result)-1, 2); + + Result := Result + ')'; + + if Algorithm <> '' then + Result := Result + ' USING ' + Algorithm; + + if not Comment.IsEmpty then + Result := Result + ' COMMENT ' + FConnection.EscapeString(Comment); + + end else begin - if IndexType <> TTableKey.KEY then + // SQLite syntax: + // CREATE INDEX myindex ON table1 ("Column 1") + // TODO: test on PG, MS, IB + Result := 'CREATE '; + if not IsIndex then Result := Result + IndexType + ' '; - Result := Result + 'INDEX ' + FConnection.QuoteIdent(Name) + ' '; - end; - Result := Result + '('; - for i:=0 to Columns.Count-1 do begin - Result := Result + FConnection.QuoteIdent(Columns[i]); - if SubParts[i] <> '' then - Result := Result + '(' + SubParts[i] + ')'; - Result := Result + ', '; + Result := Result + 'INDEX '+FConnection.QuoteIdent(Name)+' ON ' + FConnection.QuoteIdent(TableName) + ' ('; + for i:=0 to Columns.Count-1 do begin + Result := Result + FConnection.QuoteIdent(Columns[i]); + Result := Result + ', '; + end; + if Columns.Count > 0 then + Delete(Result, Length(Result)-1, 2); + Result := Result + ')'; end; - if Columns.Count > 0 then - Delete(Result, Length(Result)-1, 2); - - Result := Result + ')'; - - if Algorithm <> '' then - Result := Result + ' USING ' + Algorithm; - if not Comment.IsEmpty then - Result := Result + ' COMMENT ' + FConnection.EscapeString(Comment); end; procedure TTableKeyList.Assign(Source: TTableKeyList); @@ -10825,13 +11326,14 @@ function TForeignKey.ReferenceTableObj: TDBObject; RefTable := ReferenceTable.Substring(Length(ReferenceDb) + 1); end else begin RefDb := ReferenceTable.Substring(0, Pos('.', ReferenceTable)-1); - if not RefDb.IsEmpty then begin + if (not RefDb.IsEmpty) and (FConnection.FAllDatabases.IndexOf(RefDb) > -1) then begin RefTable := ReferenceTable.Substring(Length(RefDb)+1); end else begin RefDb := FConnection.Database; RefTable := ReferenceTable; end; end; + FConnection.Log(lcDebug, 'Find object "'+RefTable+'" in db "'+RefDb+'"'); Result := FConnection.FindObject(RefDb, RefTable); end; @@ -10911,7 +11413,8 @@ constructor TSQLFunctionList.Create(AOwner: TDBConnection; SQLFunctionsFileOrder TryFiles := Explode(',', SQLFunctionsFileOrder); for TryFile in TryFiles do begin - IniFilePath := ExtractFilePath(Application.ExeName) + 'functions-'+TryFile+'.ini'; + IniFilePath := GetAppDir + 'functions-'+TryFile+'.ini'; + FOwner.Log(lcDebug, 'Trying '+IniFilePath); if FileExists(IniFilePath) then begin FOwner.Log(lcInfo, 'Reading function definitions from '+IniFilePath); Ini := TMemIniFile.Create(IniFilePath); diff --git a/source/dbstructures.interbase.pas b/source/dbstructures.interbase.pas index ad466f929..cb2364391 100644 --- a/source/dbstructures.interbase.pas +++ b/source/dbstructures.interbase.pas @@ -4,7 +4,13 @@ interface uses - dbstructures; + dbstructures, StrUtils; + +type + TInterbaseProvider = class(TSqlProvider) + public + function GetSql(AId: TQueryId): string; override; + end; var @@ -171,4 +177,65 @@ interface implementation + +{ TInterbaseProvider } + +function TInterbaseProvider.GetSql(AId: TQueryId): string; +begin + case AId of + qDatabaseDrop: Result := 'DROP DATABASE %s'; + qEmptyTable: Result := 'TRUNCATE %s'; + qRenameTable: Result := 'RENAME TABLE %s TO %s'; + qRenameView: Result := 'RENAME TABLE %s TO %s'; + qCurrentUserHost: Result := IfThen( + FNetType in [ntInterbase_TCPIP, ntInterbase_Local], + 'select user from rdb$database', + 'select current_user || ''@'' || mon$attachments.mon$remote_host from mon$attachments where mon$attachments.mon$attachment_id = current_connection' + ); + qLikeCompare: Result := '%s LIKE %s'; + qAddColumn: Result := 'ADD COLUMN %s'; + qChangeColumn: Result := 'CHANGE COLUMN %s %s'; + qRenameColumn: Result := ''; + qSessionVariables: Result := 'SHOW VARIABLES'; + qGlobalVariables: Result := 'SHOW GLOBAL VARIABLES'; + qISSchemaCol: Result := '%s_SCHEMA'; + qUSEQuery: Result := ''; + qKillQuery: Result := 'KILL %d'; + qKillProcess: Result := 'KILL %d'; + qFuncLength: Result := 'LENGTH'; + qFuncCeil: Result := 'CEIL'; + qFuncLeft: Result := 'SUBSTR(%s, 1, %d)'; + qFuncNow: Result := ' cast(''now'' as timestamp) from rdb$database'; + qFuncLastAutoIncNumber: Result := 'LAST_INSERT_ID()'; + qLockedTables: Result := ''; + qDisableForeignKeyChecks: Result := ''; + qEnableForeignKeyChecks: Result := ''; + qForeignKeyDrop: Result := 'DROP FOREIGN KEY %s'; + qGetTableColumns: Result := 'SELECT r.RDB$FIELD_NAME AS field_name,'+ + ' r.RDB$DESCRIPTION AS field_description,'+ + ' r.RDB$DEFAULT_VALUE AS field_default_value,'+ + ' r.RDB$NULL_FLAG AS null_flag,'+ + ' f.RDB$FIELD_LENGTH AS field_length,'+ + ' f.RDB$FIELD_PRECISION AS field_precision,'+ + ' f.RDB$FIELD_SCALE AS field_scale,'+ + ' f.RDB$FIELD_TYPE AS field_type,'+ + ' f.RDB$FIELD_SUB_TYPE AS field_subtype,'+ + ' coll.RDB$COLLATION_NAME AS field_collation,'+ + ' cset.RDB$CHARACTER_SET_NAME AS field_charset'+ + ' FROM RDB$RELATION_FIELDS r'+ + ' LEFT JOIN RDB$FIELDS f ON r.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME'+ + ' LEFT JOIN RDB$CHARACTER_SETS cset ON f.RDB$CHARACTER_SET_ID = cset.RDB$CHARACTER_SET_ID'+ + ' LEFT JOIN RDB$COLLATIONS coll ON f.RDB$COLLATION_ID = coll.RDB$COLLATION_ID'+ + ' AND F.RDB$CHARACTER_SET_ID = COLL.RDB$CHARACTER_SET_ID'+ + ' WHERE r.RDB$RELATION_NAME=%s'+ + ' ORDER BY r.RDB$FIELD_POSITION'; + qGetCollations: Result := 'SELECT RDB$COLLATION_NAME AS "Collation",'+ + ' RDB$COLLATION_ID AS "Id",'+ + ' RDB$CHARACTER_SET_ID'+ + ' FROM RDB$COLLATIONS'; + qGetCharsets: Result := 'SELECT RDB$CHARACTER_SET_NAME AS "Charset", RDB$CHARACTER_SET_NAME AS "Description" FROM RDB$CHARACTER_SETS'; + end; +end; + + end. \ No newline at end of file diff --git a/source/dbstructures.mssql.pas b/source/dbstructures.mssql.pas index 2072334ba..c10bb5793 100644 --- a/source/dbstructures.mssql.pas +++ b/source/dbstructures.mssql.pas @@ -3,7 +3,13 @@ interface uses - dbstructures; + dbstructures, StrUtils; + +type + TMsSqlProvider = class(TSqlProvider) + public + function GetSql(AId: TQueryId): string; override; + end; var @@ -407,4 +413,76 @@ interface implementation + +function TMsSqlProvider.GetSql(AId: TQueryId): string; +begin + case AId of + qDatabaseTable: Result := IfThen( + ServerVersion<=899, + 'master..sysdatabases', + 'sys.databases' + ); + qDatabaseTableId: Result := IfThen( + ServerVersion<=899, + 'dbid', + 'database_id' + ); + qDatabaseDrop: Result := 'DROP DATABASE %s'; + qDbObjectsTable: Result := IfThen( + ServerVersion<=899, + '..sysobjects', + '.sys.objects' + ); + qDbObjectsCreateCol: Result := IfThen( + ServerVersion<=899, + 'crdate', + 'create_date' + ); + qDbObjectsUpdateCol: Result := IfThen( + ServerVersion<=899, + '', + 'modify_date' + ); + qDbObjectsTypeCol: Result := IfThen( + ServerVersion<=899, + 'xtype', + 'type' + ); + qRenameTable: Result := 'EXEC sp_rename %s, %s'; + qRenameView: Result := 'EXEC sp_rename %s, %s'; + qCurrentUserHost: Result := 'SELECT SYSTEM_USER'; + qLikeCompare: Result := '%s LIKE %s'; + qAddColumn: Result := 'ADD %s'; + qChangeColumn: Result := 'ALTER COLUMN %s %s'; + qSessionVariables: Result := 'SELECT comment, value FROM master.dbo.syscurconfigs ORDER BY comment'; + qGlobalVariables: Result := 'SELECT comment, value FROM master.dbo.syscurconfigs ORDER BY comment'; + qISSchemaCol: Result := '%s_CATALOG'; + qUSEQuery: Result := 'USE %s'; + qKillQuery: Result := 'KILL %d'; + qKillProcess: Result := 'KILL %d'; + qFuncLength: Result := 'LEN'; + qFuncCeil: Result := 'CEILING'; + qFuncLeft: Result := 'LEFT(%s, %d)'; + qFuncNow: Result := 'GETDATE()'; + qFuncLastAutoIncNumber: Result := 'LAST_INSERT_ID()'; + qLockedTables: Result := ''; + qDisableForeignKeyChecks: Result := ''; + qEnableForeignKeyChecks: Result := ''; + qForeignKeyDrop: Result := 'DROP FOREIGN KEY %s'; + qGetTableColumns: Result := ''; + qGetCollations: Result := 'SELECT '''' AS "Collation", '+ + ''''' AS "Charset", 0 AS "Id", '+ + ''''' AS "Default", '''' AS "Compiled", '+ + '1 AS "Sortlen"'; + qGetCharsets: Result := 'SELECT name AS Charset, description AS Description FROM master.sys.syscharsets'; + qGetRowCountApprox: Result := IfThen( + FServerVersion >= 900, + 'SELECT SUM("rows") FROM "sys"."partitions" WHERE "index_id" IN (0, 1) AND "object_id" = object_id(:EscapedDbSchemaName)', + '' + ); + else Result := inherited; + end; +end; + + end. diff --git a/source/dbstructures.mysql.pas b/source/dbstructures.mysql.pas index c05246252..6020fa61c 100644 --- a/source/dbstructures.mysql.pas +++ b/source/dbstructures.mysql.pas @@ -4,7 +4,7 @@ interface uses - System.Classes, dbstructures; + System.Classes, System.SysUtils, dbstructures, StrUtils; const @@ -259,83 +259,6 @@ MYSQL_RES = record end; PMYSQL_RES = ^MYSQL_RES; - TMySQLOption = ( - MYSQL_OPT_CONNECT_TIMEOUT, - MYSQL_OPT_COMPRESS, - MYSQL_OPT_NAMED_PIPE, - MYSQL_INIT_COMMAND, - MYSQL_READ_DEFAULT_FILE, - MYSQL_READ_DEFAULT_GROUP, - MYSQL_SET_CHARSET_DIR, - MYSQL_SET_CHARSET_NAME, - MYSQL_OPT_LOCAL_INFILE, - MYSQL_OPT_PROTOCOL, - MYSQL_SHARED_MEMORY_BASE_NAME, - MYSQL_OPT_READ_TIMEOUT, - MYSQL_OPT_WRITE_TIMEOUT, - MYSQL_OPT_USE_RESULT, - MYSQL_OPT_USE_REMOTE_CONNECTION, - MYSQL_OPT_USE_EMBEDDED_CONNECTION, - MYSQL_OPT_GUESS_CONNECTION, - MYSQL_SET_CLIENT_IP, - MYSQL_SECURE_AUTH, - MYSQL_REPORT_DATA_TRUNCATION, - MYSQL_OPT_RECONNECT, - MYSQL_OPT_SSL_VERIFY_SERVER_CERT, - MYSQL_PLUGIN_DIR, - MYSQL_DEFAULT_AUTH, - MYSQL_OPT_BIND, - MYSQL_OPT_SSL_KEY, - MYSQL_OPT_SSL_CERT, - MYSQL_OPT_SSL_CA, - MYSQL_OPT_SSL_CAPATH, - MYSQL_OPT_SSL_CIPHER, - MYSQL_OPT_SSL_CRL, - MYSQL_OPT_SSL_CRLPATH, - // Connection attribute options - MYSQL_OPT_CONNECT_ATTR_RESET, - MYSQL_OPT_CONNECT_ATTR_ADD, - MYSQL_OPT_CONNECT_ATTR_DELETE, - MYSQL_SERVER_PUBLIC_KEY, - MYSQL_ENABLE_CLEARTEXT_PLUGIN, - MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, - MYSQL_OPT_SSL_ENFORCE, - MYSQL_OPT_MAX_ALLOWED_PACKET, - MYSQL_OPT_NET_BUFFER_LENGTH, - MYSQL_OPT_TLS_VERSION, - MYSQL_OPT_SSL_MODE, - MYSQL_OPT_GET_SERVER_PUBLIC_KEY, - - // MariaDB specific - MYSQL_PROGRESS_CALLBACK=5999, - MYSQL_OPT_NONBLOCK, - // MariaDB Connector/C specific - MYSQL_DATABASE_DRIVER=7000, - MARIADB_OPT_SSL_FP, // deprecated, use MARIADB_OPT_TLS_PEER_FP instead - MARIADB_OPT_SSL_FP_LIST, // deprecated, use MARIADB_OPT_TLS_PEER_FP_LIST instead - MARIADB_OPT_TLS_PASSPHRASE, // passphrase for encrypted certificates - MARIADB_OPT_TLS_CIPHER_STRENGTH, - MARIADB_OPT_TLS_VERSION, - MARIADB_OPT_TLS_PEER_FP, // single finger print for server certificate verification - MARIADB_OPT_TLS_PEER_FP_LIST, // finger print white list for server certificate verification - MARIADB_OPT_CONNECTION_READ_ONLY, - MYSQL_OPT_CONNECT_ATTRS, // for mysql_get_optionv - MARIADB_OPT_USERDATA, - MARIADB_OPT_CONNECTION_HANDLER, - MARIADB_OPT_PORT, - MARIADB_OPT_UNIXSOCKET, - MARIADB_OPT_PASSWORD, - MARIADB_OPT_HOST, - MARIADB_OPT_USER, - MARIADB_OPT_SCHEMA, - MARIADB_OPT_DEBUG, - MARIADB_OPT_FOUND_ROWS, - MARIADB_OPT_MULTI_RESULTS, - MARIADB_OPT_MULTI_STATEMENTS, - MARIADB_OPT_INTERACTIVE, - MARIADB_OPT_PROXY_HEADER - ); - TMySQLLib = class(TDbLib) mysql_affected_rows: function(Handle: PMYSQL): Int64; stdcall; mysql_character_set_name: function(Handle: PMYSQL): PAnsiChar; stdcall; @@ -351,14 +274,14 @@ TMySQLLib = class(TDbLib) mysql_get_client_info: function: PAnsiChar; stdcall; mysql_get_server_info: function(Handle: PMYSQL): PAnsiChar; stdcall; mysql_init: function(Handle: PMYSQL): PMYSQL; stdcall; + mysql_info: function(Handle: PMYSQL): PAnsiChar; stdcall; mysql_num_fields: function(Result: PMYSQL_RES): Integer; stdcall; mysql_num_rows: function(Result: PMYSQL_RES): Int64; stdcall; - mysql_options: function(Handle: PMYSQL; Option: Integer; arg: PAnsiChar): Integer; stdcall; + mysql_options: function(Handle: PMYSQL; Option: Integer; arg: Pointer): Integer; stdcall; mysql_optionsv: function(Handle: PMYSQL; Option: Integer; arg, val: PAnsiChar): Integer; stdcall; mysql_ping: function(Handle: PMYSQL): Integer; stdcall; mysql_real_connect: function(Handle: PMYSQL; const Host, User, Passwd, Db: PAnsiChar; Port: Cardinal; const UnixSocket: PAnsiChar; ClientFlag: Cardinal): PMYSQL; stdcall; mysql_real_query: function(Handle: PMYSQL; const Query: PAnsiChar; Length: Cardinal): Integer; stdcall; - mysql_ssl_set: function(Handle: PMYSQL; const key, cert, CA, CApath, cipher: PAnsiChar): Byte; stdcall; mysql_stat: function(Handle: PMYSQL): PAnsiChar; stdcall; mysql_store_result: function(Handle: PMYSQL): PMYSQL_RES; stdcall; mysql_thread_id: function(Handle: PMYSQL): Cardinal; stdcall; @@ -367,16 +290,48 @@ TMySQLLib = class(TDbLib) mysql_thread_init: function: Byte; stdcall; mysql_thread_end: procedure; stdcall; mysql_warning_count: function(Handle: PMYSQL): Cardinal; stdcall; + const + INVALID_OPT = -1; + MYBOOL_FALSE: Integer = 0; + MYBOOL_TRUE: Integer = 1; protected procedure AssignProcedures; override; + public + MYSQL_OPT_LOCAL_INFILE, + MYSQL_OPT_CONNECT_TIMEOUT, + MARIADB_OPT_TLS_VERSION, + MYSQL_OPT_TLS_VERSION, + MYSQL_PLUGIN_DIR, + MYSQL_OPT_SSL_KEY, + MYSQL_OPT_SSL_CERT, + MYSQL_OPT_SSL_CA, + MYSQL_OPT_SSL_CIPHER, + MYSQL_OPT_CONNECT_ATTR_ADD, + MYSQL_ENABLE_CLEARTEXT_PLUGIN, + MYSQL_OPT_SSL_MODE, + MYSQL_OPT_SSL_VERIFY_SERVER_CERT: Integer; + SSL_MODE_DISABLED, + SSL_MODE_PREFERRED, + SSL_MODE_REQUIRED, + SSL_MODE_VERIFY_CA, + SSL_MODE_VERIFY_IDENTITY: Integer; + constructor Create(DllFile, DefaultDll: String); override; + function IsLibMariadb: Boolean; + end; + + TMySqlProvider = class(TSqlProvider) + public + function GetSql(AId: TQueryId): string; override; end; + + var MySQLKeywords: TStringList; MySQLErrorCodes: TStringList; // MySQL Data Type List and Properties - MySQLDatatypes: array [0..38] of TDBDatatype = + MySQLDatatypes: array [0..42] of TDBDatatype = ( ( Index: dbdtUnknown; @@ -405,6 +360,19 @@ TMySQLLib = class(TDbLib) LoadPart: False; Category: dtcInteger; ), + ( + Index: dbdtBool; + NativeType: 1; + Name: 'BOOLEAN'; + Description: 'Synonym of TINYINT(1)'; + HasLength: False; + RequiresLength: False; + MaxSize: 127; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcInteger; + ), ( Index: dbdtSmallint; NativeType: 2; @@ -737,7 +705,7 @@ TMySQLLib = class(TDbLib) RequiresLength: False; HasBinary: False; HasDefault: False; - LoadPart: False; + LoadPart: True; Category: dtcText; ), ( @@ -755,6 +723,39 @@ TMySQLLib = class(TDbLib) LoadPart: False; Category: dtcText; ), + ( + Index: dbdtInet4; + NativeType: 255; + Name: 'INET4'; + Description: 'INET4' + sLineBreak + + 'INET4 is a data type to store IPv4 addresses, as 4-byte binary strings. '+ + 'It was added in MariaDB 10.10.0'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcText; + MinVersion: 10100; + ), + ( + Index: dbdtInet6; + NativeType: 255; + Name: 'INET6'; + Description: 'INET6' + sLineBreak + + 'The INET6 data type is intended for storage of IPv6 addresses, as well as ' + + 'IPv4 addresses assuming conventional mapping of IPv4 addresses into IPv6 ' + + 'addresses. ' + slineBreak + + 'Both short and long IPv6 notation are permitted, according to RFC-5952. '+ + 'It was added in MariaDB 10.5.0'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcText; + MinVersion: 10050; + ), ( Index: dbdtBinary; NativeType: 254; @@ -854,6 +855,20 @@ TMySQLLib = class(TDbLib) LoadPart: True; Category: dtcBinary; ), + ( + Index: dbdtVector; + NativeType: 253; + Name: 'VECTOR'; + Description: 'VECTOR(N)' + sLineBreak + + 'VECTOR data type with a built-in data validation. N is the number of dimensions ' + + 'that all vector values in the column will have.'; + HasLength: True; + RequiresLength: True; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcBinary; + ), ( Index: dbdtEnum; NativeType: 247; @@ -3137,6 +3152,54 @@ implementation uses apphelpers; +constructor TMySQLLib.Create(DllFile, DefaultDll: String); +begin + inherited; + // MYSQL_OPT_* constants + MYSQL_OPT_CONNECT_TIMEOUT := 0; + MYSQL_OPT_LOCAL_INFILE := 8; + MYSQL_PLUGIN_DIR := 22; + MYSQL_OPT_SSL_KEY := 25; + MYSQL_OPT_SSL_CERT := 26; + MYSQL_OPT_SSL_CA := 27; + MYSQL_OPT_SSL_CIPHER := 29; + MYSQL_OPT_CONNECT_ATTR_ADD := 33; + MYSQL_ENABLE_CLEARTEXT_PLUGIN := 36; + MYSQL_OPT_TLS_VERSION := 41; + MARIADB_OPT_TLS_VERSION := INVALID_OPT; + MYSQL_OPT_SSL_MODE := INVALID_OPT; + MYSQL_OPT_SSL_VERIFY_SERVER_CERT := INVALID_OPT; + // Option values + SSL_MODE_DISABLED := 1; + SSL_MODE_PREFERRED := 2; + SSL_MODE_REQUIRED := 3; + SSL_MODE_VERIFY_CA := 4; + SSL_MODE_VERIFY_IDENTITY := 5; + if IsLibMariadb then begin + // Differences in libmariadb + MYSQL_OPT_SSL_VERIFY_SERVER_CERT := 21; + MARIADB_OPT_TLS_VERSION := 7005; + end + else if String(mysql_get_client_info).StartsWith('8.') then begin + // Some constants were removed in MySQL 8.0, so the offsets differ + MYSQL_PLUGIN_DIR := 16; + MYSQL_OPT_SSL_KEY := 19; + MYSQL_OPT_SSL_CERT := 20; + MYSQL_OPT_SSL_CA := 21; + MYSQL_OPT_SSL_CIPHER := 23; + MYSQL_OPT_CONNECT_ATTR_ADD := 27; + MYSQL_ENABLE_CLEARTEXT_PLUGIN := 30; + MYSQL_OPT_TLS_VERSION := 34; + MYSQL_OPT_SSL_MODE := 35; + end; +end; + +function TMySQLLib.IsLibMariadb: Boolean; +begin + // libmariadb used (not libmysql) ? + Result := ExtractFileName(FDllFile).StartsWith('libmariadb', True); +end; + procedure TMySQLLib.AssignProcedures; begin AssignProc(@mysql_affected_rows, 'mysql_affected_rows'); @@ -3153,6 +3216,7 @@ procedure TMySQLLib.AssignProcedures; AssignProc(@mysql_get_client_info, 'mysql_get_client_info'); AssignProc(@mysql_get_server_info, 'mysql_get_server_info'); AssignProc(@mysql_init, 'mysql_init'); + AssignProc(@mysql_info, 'mysql_info'); AssignProc(@mysql_num_fields, 'mysql_num_fields'); AssignProc(@mysql_num_rows, 'mysql_num_rows'); AssignProc(@mysql_ping, 'mysql_ping'); @@ -3160,7 +3224,6 @@ procedure TMySQLLib.AssignProcedures; AssignProc(@mysql_optionsv, 'mysql_optionsv', False); AssignProc(@mysql_real_connect, 'mysql_real_connect'); AssignProc(@mysql_real_query, 'mysql_real_query'); - AssignProc(@mysql_ssl_set, 'mysql_ssl_set'); AssignProc(@mysql_stat, 'mysql_stat'); AssignProc(@mysql_store_result, 'mysql_store_result'); AssignProc(@mysql_thread_id, 'mysql_thread_id'); @@ -3172,46 +3235,293 @@ procedure TMySQLLib.AssignProcedures; end; +{ TMySqlProvider } + +function TMySqlProvider.GetSql(AId: TQueryId): string; +var + IsMariaDB: Boolean; +begin + IsMariaDB := ServerVersion >= 100000; + case AId of + qDatabaseDrop: Result := 'DROP DATABASE %s'; + qEmptyTable: Result := 'TRUNCATE %s'; + qRenameTable: Result := 'RENAME TABLE %s TO %s'; + qRenameView: Result := 'RENAME TABLE %s TO %s'; + qCurrentUserHost: Result := 'SELECT CURRENT_USER()'; + qLikeCompare: Result := '%s LIKE %s'; + qAddColumn: Result := 'ADD COLUMN %s'; + qChangeColumn: Result := 'CHANGE COLUMN %s %s'; + qGlobalStatus: Result := IfThen( + FNetType = ntMySQL_ProxySQLAdmin, + 'SELECT * FROM stats_mysql_global', + 'SHOW /*!50002 GLOBAL */ STATUS' + ); + qCommandsCounters: Result := IfThen( + FNetType = ntMySQL_ProxySQLAdmin, + 'SELECT * FROM stats_mysql_commands_counters', + 'SHOW /*!50002 GLOBAL */ STATUS LIKE ''Com\_%''' + ); + qSessionVariables: Result := 'SHOW VARIABLES'; + qGlobalVariables: Result := 'SHOW GLOBAL VARIABLES'; + qISSchemaCol: Result := '%s_SCHEMA'; + qUSEQuery: Result := 'USE %s'; + qKillQuery: Result := IfThen( + FNetType = ntMySQL_RDS, + 'CALL mysql.rds_kill_query(%d)', + IfThen( + FServerVersion >= 50000, + 'KILL QUERY %d', + 'KILL %d' + ) + ); + qKillProcess: Result := IfThen( + FNetType = ntMySQL_RDS, + 'CALL mysql.rds_kill(%d)', + 'KILL %d' + ); + qFuncLength: Result := 'LENGTH'; + qFuncCeil: Result := 'CEIL'; + qFuncLeft: Result := IfThen( + FNetType = ntMySQL_ProxySQLAdmin, + 'SUBSTR(%s, 1, %d)', + 'LEFT(%s, %d)' + ); + qFuncNow: Result := IfThen( + FNetType = ntMySQL_ProxySQLAdmin, + 'CURRENT_TIMESTAMP', + 'NOW()' + ); + qFuncLastAutoIncNumber: Result := 'LAST_INSERT_ID()'; + qLockedTables: Result := IfThen( + (FNetType <> ntMySQL_ProxySQLAdmin) and (FServerVersion >= 50124), + 'SHOW OPEN TABLES FROM %s WHERE in_use!=0', + '' + ); + qDisableForeignKeyChecks: Result := IfThen( + FServerVersion >= 40014, + 'SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0', + '' + ); + qEnableForeignKeyChecks: Result := IfThen( + FServerVersion >= 40014, + 'SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1)', + '' + ); + qForeignKeyDrop: Result := 'DROP FOREIGN KEY %s'; + qGetTableColumns: Result := ''; + qGetCollations: Result := IfThen( + FServerVersion >= 40100, + 'SHOW COLLATION', + '' + ); + // Issue #1917: MariaDB 10.10.1+ versions have additional collations in IS.COLLATION_CHARACTER_SET_APPLICABILITY + qGetCollationsExtended: Result := IfThen( + FServerVersion >= 101001, + 'SELECT'+ + ' FULL_COLLATION_NAME AS `Collation`'+ + ', CHARACTER_SET_NAME AS `Charset`'+ + ', ID AS `Id`'+ + ', IS_DEFAULT AS `Default`'+ + ', 0 AS `Sortlen`'+ + ' FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY'+ + ' ORDER BY `Collation`', + '' + ); + qGetCharsets: Result := IfThen( + FServerVersion >= 40100, + 'SHOW CHARSET', + '' + ); + qGetRowCountApprox: Result := IfThen( + FNetType <> ntMySQL_ProxySQLAdmin, + 'SHOW TABLE STATUS FROM :QuotedDatabase LIKE :EscapedName', + '' + ); + qGetReverseForeignKeys: Result := 'SELECT DISTINCT'+ + ' k.TABLE_SCHEMA, k.TABLE_NAME'+ + ' FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k'+ + ' WHERE'+ + ' REFERENCED_TABLE_SCHEMA = :EscapedDatabase AND'+ + ' REFERENCED_TABLE_NAME = :EscapedName'; + qExplain: Result := IfThen( + (FServerVersion >= 80400) and (FServerVersion < 100000), // Not MariaDB + 'EXPLAIN FORMAT=TRADITIONAL %s', + 'EXPLAIN %s' + ); + qSetTimezone: Result := IfThen( + FServerVersion >= 40103, + 'SET time_zone=%s', + '' + ); + qShowFunctionStatus: Result := IfThen( + (FServerVersion >= 50000) and (FNetType <> ntMySQL_ProxySQLAdmin), + 'SHOW FUNCTION STATUS WHERE Db = %s', + '' + ); + qShowProcedureStatus: Result := IfThen( + (FServerVersion >= 50000) and (FNetType <> ntMySQL_ProxySQLAdmin), + 'SHOW PROCEDURE STATUS WHERE Db = %s', + '' + ); + qShowTriggers: Result := IfThen( + (FServerVersion >= 50010) and (FNetType <> ntMySQL_ProxySQLAdmin), + 'SHOW TRIGGERS FROM %s', + '' + ); + qShowEvents: Result := IfThen( + (FServerVersion >= 50010) and (FNetType <> ntMySQL_ProxySQLAdmin), + 'SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM INFORMATION_SCHEMA.`EVENTS` WHERE EVENT_SCHEMA=%s', + '' + ); + qHelpKeyword: Result := IfThen( + (FServerVersion >= 40100) and (FNetType <> ntMySQL_ProxySQLAdmin), + 'HELP %s', + '' + ); + qShowCreateTrigger: Result := IfThen( + FServerVersion >= 50121, + 'SHOW CREATE TRIGGER :QuotedDatabase.:QuotedName', + '' + ); + qShowWarnings: Result := IfThen( + FServerVersion >= 40100, + 'SHOW WARNINGS', + '' + ); + qDropUser: Result := IfThen( + FServerVersion < 40101, + 'DELETE FROM mysql.user WHERE User=%s AND Host=%s', + 'DROP USER %s@%s' + ); + qCreateRole: Result := 'CREATE ROLE %s'; + qDropRole: Result := 'DROP ROLE %s'; + qReloadPrivileges: Result := 'FLUSH PRIVILEGES'; + qGrantRole: Result := 'GRANT %s TO %s%s'; + qRevokeRole: Result := 'REVOKE %s FROM %s'; + qSetDefaultRole: Result := 'SET DEFAULT ROLE %s FOR %s'; + qIndexVisible: Result := IfThen( + FServerVersion >= 100600, // mariadb + 'NOT IGNORED', + IfThen( + FServerVersion >= 80000, // mysql + 'VISIBLE', + '' + ) + ); + qIndexInvisible: Result := IfThen( + FServerVersion >= 100600, // mariadb + 'IGNORED', + IfThen( + FServerVersion >= 80000, // mysql + 'INVISIBLE', + '' + ) + ); + qGetAuthPlugins: Result := IfThen( + (FServerVersion >= 50100) or IsMariaDB, // mysql 5.1+ and all mariadb versions + 'SELECT PLUGIN_NAME FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_TYPE=''AUTHENTICATION'' AND PLUGIN_STATUS=''ACTIVE''', + '' + ); + else Result := inherited; + end; +end; + + initialization // Keywords copied from SynHighligherSQL MySQLKeywords := TStringList.Create; -MySQLKeywords.CommaText := 'ACCESSIBLE,ACTION,ADD,AFTER,AGAINST,AGGREGATE,ALGORITHM,ALL,ALTER,ANALYZE,AND,ANY,AS,' + - 'ASC,ASENSITIVE,AT,AUTO_INCREMENT,AVG_ROW_LENGTH,BACKUP,BEFORE,BEGIN,BENCHMARK,BETWEEN,BINLOG,BIT,' + - 'BOOL,BOTH,BY,CACHE,CALL,CASCADE,CASCADED,CASE,CHANGE,CHARACTER,CHARSET,CHECK,' + - 'CHECKSUM,CLIENT,COLLATE,COLLATION,COLUMN,COLUMNS,COMMENT,COMMIT,' + - 'COMMITTED,COMPLETION,CONCURRENT,CONNECTION,CONSISTENT,CONSTRAINT,' + - 'CONVERT,CONTAINS,CONTENTS,CREATE,CROSS,DATA,DATABASE,DATABASES,DAY_HOUR,' + - 'DAY_MICROSECOND,DAY_MINUTE,DAY_SECOND,DEALLOCATE,DEC,DEFAULT,DEFINER,DELAYED,DELAY_KEY_WRITE,DELETE,DESC,' + - 'DETERMINISTIC,DIRECTORY,DISABLE,DISCARD,DESCRIBE,DISTINCT,DISTINCTROW,' + - 'DIV,DROP,DUAL,DUMPFILE,DUPLICATE,EACH,ELSE,ELSEIF,ENABLE,ENCLOSED,END,ENDS,' + - 'ENGINE,ENGINES,ESCAPE,ESCAPED,ERRORS,EVENT,EVENTS,EVERY,EXECUTE,EXISTS,' + - 'EXPANSION,EXPLAIN,FALSE,FIELDS,FILE,FIRST,FLOAT4,FLOAT8,FLUSH,FOR,FORCE,FOREIGN,FROM,' + - 'FULL,FULLTEXT,FUNCTION,FUNCTIONS,GLOBAL,GRANT,GRANTS,GROUP,HAVING,HELP,' + - 'HIGH_PRIORITY,HOSTS,HOUR_MICROSECOND,HOUR_MINUTE,HOUR_SECOND,IDENTIFIED,IGNORE,IGNORE_SERVER_IDS,INDEX,INFILE,INNER,INOUT,INSENSITIVE,INSERT,' + - 'INSERT_METHOD,INSTALL,INT1,INT2,INT3,INT4,INT8,INTEGER,INTO,IO_THREAD,IS,' + - 'ISOLATION,INVOKER,JOIN,KEY,KEYS,KILL,LAST,LEADING,LEAVES,LEVEL,LESS,' + - 'LIKE,LIMIT,LINEAR,LINES,LIST,LOAD,LOCAL,LOCK,LOGS,LONG,LOW_PRIORITY,' + - 'MASTER,MASTER_HOST,MASTER_HEARTBEAT_PERIOD,MASTER_LOG_FILE,MASTER_LOG_POS,MASTER_CONNECT_RETRY,' + - 'MASTER_PASSWORD,MASTER_PORT,MASTER_SSL,MASTER_SSL_CA,MASTER_SSL_CAPATH,' + - 'MASTER_SSL_CERT,MASTER_SSL_CIPHER,MASTER_SSL_KEY,MASTER_SSL_VERIFY_SERVER_CERT,MASTER_USER,MATCH,' + - 'MAX_ROWS,MAXVALUE,MIDDLEINT,MIN_ROWS,MINUTE_MICROSECOND,MINUTE_SECOND,MOD,MODE,MODIFY,MODIFIES,NAMES,' + - 'NATURAL,NEW,NO,NODEGROUP,NOT,NO_WRITE_TO_BINLOG,NULL,NUMERIC,OJ,OFFSET,OLD,ON,OPTIMIZE,OPTION,' + - 'OPTIONALLY,OPEN,OR,ORDER,OUT,OUTER,OUTFILE,PACK_KEYS,PARTIAL,PARTITION,' + - 'PARTITIONS,PERSISTENT,PLUGIN,PLUGINS,PRECISION,PREPARE,PRESERVE,PRIMARY,PRIVILEGES,PROCEDURE,' + - 'PROCESS,PROCESSLIST,PURGE,QUERY,RAID_CHUNKS,RAID_CHUNKSIZE,RAID_TYPE,RANGE,' + - 'READ,READS,READ_WRITE,REAL,REBUILD,REFERENCES,REGEXP,RELAY_LOG_FILE,RELAY_LOG_POS,RELEASE,RELOAD,' + - 'RENAME,REORGANIZE,REPAIR,REPEATABLE,REPLACE,REPLICATION,REQUIRE,RESIGNAL,RESTRICT,RESET,' + - 'RESTORE,RETURN,RETURNS,REVOKE,RLIKE,ROLLBACK,ROLLUP,ROUTINE,ROW,' + - 'ROW_FORMAT,ROWS,SAVEPOINT,SCHEDULE,SCHEMA,SCHEMAS,SECOND_MICROSECOND,SECURITY,SELECT,' + - 'SENSITIVE,SEPARATOR,SERIALIZABLE,SESSION,SET,SHARE,SHOW,SHUTDOWN,SIGNAL,SIMPLE,SLAVE,SNAPSHOT,SOME,' + - 'SONAME,SPECIFIC,SQL,SQLEXCEPTION,SQLSTATE,SQLWARNING,SQL_BIG_RESULT,SQL_BUFFER_RESULT,SQL_CACHE,' + - 'SQL_CALC_FOUND_ROWS,SQL_NO_CACHE,SQL_SMALL_RESULT,SPATIAL,SQL_THREAD,SSL,START,' + - 'STARTING,STARTS,STATUS,STOP,STORAGE,STRAIGHT_JOIN,SUBPARTITION,' + - 'SUBPARTITIONS,SUPER,TABLE,TABLES,TABLESPACE,TEMPORARY,TERMINATED,THAN,' + - 'THEN,TO,TRAILING,TRANSACTION,TRIGGER,TRIGGERS,TRUE,TYPE,UNCOMMITTED,UNDO,' + - 'UNINSTALL,UNIQUE,UNLOCK,UNSIGNED,UPDATE,UPGRADE,UNION,USAGE,USE,USING,VALUES,VARCHARACTER,' + - 'VARIABLES,VARYING,VIEW,VIRTUAL,WARNINGS,WHEN,WHERE,WITH,WORK,WRITE,XOR,YEAR_MONTH,ZEROFILL,' +MySQLKeywords.CommaText := 'ACCESSIBLE,ACCOUNT,ACTION,ACTIVE,ADD,ADMIN,AFTER,AGAINST,AGGREGATE,' + + 'ALGORITHM,ALL,ALTER,ALWAYS,ANALYSE,ANALYZE,AND,ANY,ARRAY,AS,ASC,' + + 'ASENSITIVE,AT,ATTRIBUTE,AUTHENTICATION,AUTOEXTEND_SIZE,AUTO_INCREMENT,' + + 'AVG_ROW_LENGTH,BACKUP,BEFORE,BEGIN,BETWEEN,BINLOG,BIT,BLOCK,BOTH,BUCKETS,' + + 'BULK,BY,CACHE,CALL,CASCADE,CASCADED,CATALOG_NAME,CHAIN,CHALLENGE_RESPONSE,' + + 'CHANGE,CHANGED,CHANNEL,CHARACTER,CHARSET,CHECK,CHECKSUM,CIPHER,' + + 'CLASS_ORIGIN,CLIENT,CLONE,CODE,COLLATE,COLLATION,COLUMN,COLUMNS,' + + 'COLUMN_FORMAT,COLUMN_NAME,COMMENT,COMMIT,COMMITTED,COMPLETION,COMPONENT,' + + 'COMPRESSION,CONCURRENT,CONDITION,CONNECTION,CONSISTENT,CONSTRAINT,' + + 'CONSTRAINT_CATALOG,CONSTRAINT_NAME,CONSTRAINT_SCHEMA,CONTAINS,CONTEXT,' + + 'CONTINUE,CONVERT,CPU,CREATE,CROSS,CUBE,CUME_DIST,CURRENT,CURSOR,' + + 'CURSOR_NAME,DATA,DATABASE,DATABASES,DATAFILE,DAY_HOUR,DAY_MICROSECOND,' + + 'DAY_MINUTE,DAY_SECOND,DEALLOCATE,DEC,DECLARE,DEFAULT,DEFAULT_AUTH,DEFINER,' + + 'DEFINITION,DELAYED,DELAY_KEY_WRITE,DELETE,DENSE_RANK,DESC,DESCRIBE,' + + 'DESCRIPTION,DES_KEY_FILE,DETERMINISTIC,DIAGNOSTICS,DIRECTORY,DISABLE,' + + 'DISCARD,DISTINCT,DISTINCTROW,DIV,DO,DROP,DUAL,DUMPFILE,DUPLICATE,EACH,' + + 'ELSE,ELSEIF,EMPTY,ENABLE,ENCLOSED,ENCRYPTION,END,ENDS,ENFORCED,ENGINE,' + + 'ENGINES,ENGINE_ATTRIBUTE,ERROR,ERRORS,ESCAPE,ESCAPED,EVENT,EVENTS,EVERY,' + + 'EXCEPT,EXCHANGE,EXCLUDE,EXECUTE,EXISTS,EXPANSION,EXPIRE,EXPLAIN,EXPORT,' + + 'EXTENDED,EXTENT_SIZE,FACTOR,FAILED_LOGIN_ATTEMPTS,FALSE,FAST,FAULTS,' + + 'FIELDS,FILE,FILE_BLOCK_SIZE,FILTER,FINISH,FIRST,FIRST_VALUE,FLOAT4,FLOAT8,' + + 'FLUSH,FOLLOWING,FOLLOWS,FOR,FORCE,FOREIGN,FOUND,FROM,FULL,FULLTEXT,' + + 'FUNCTION,GENERAL,GENERATE,GENERATED,GEOMCOLLECTION,GET,' + + 'GET_MASTER_PUBLIC_KEY,GET_SOURCE_PUBLIC_KEY,GLOBAL,GRANT,GRANTS,GROUP,' + + 'GROUPING,GROUPS,GROUP_REPLICATION,GTID_ONLY,HAVING,HELP,HIGH_PRIORITY,' + + 'HISTOGRAM,HISTORY,HOST,HOSTS,HOUR_MICROSECOND,HOUR_MINUTE,HOUR_SECOND,' + + 'IDENTIFIED,IGNORE,IGNORE_SERVER_IDS,IMPORT,IN,INACTIVE,INDEX,INDEXES,' + + 'INFILE,INITIAL,INITIAL_SIZE,INITIATE,INNER,INOUT,INSENSITIVE,INSERT,' + + 'INSERT_METHOD,INSTALL,INSTANCE,INT1,INT2,INT3,INT4,INT8,INTERSECT,INTO,' + + 'INVISIBLE,INVOKER,IO,IO_AFTER_GTIDS,IO_BEFORE_GTIDS,IO_THREAD,IPC,IS,' + + 'ISOLATION,ISSUER,JOIN,JSON,JSON_TABLE,JSON_VALUE,KEY,KEYRING,KEYS,' + + 'KEY_BLOCK_SIZE,KILL,LAG,LANGUAGE,LAST,LAST_VALUE,LATERAL,LEAD,LEADING,' + + 'LEAVES,LESS,LEVEL,LIKE,LIMIT,LINEAR,LINES,LIST,LOAD,LOCAL,LOCK,LOCKED,' + + 'LOCKS,LOGFILE,LOGS,LONG,LOW_PRIORITY,MASTER,MASTER_AUTO_POSITION,' + + 'MASTER_BIND,MASTER_COMPRESSION_ALGORITHMS,MASTER_CONNECT_RETRY,' + + 'MASTER_DELAY,MASTER_HEARTBEAT_PERIOD,MASTER_HOST,MASTER_LOG_FILE,' + + 'MASTER_LOG_POS,MASTER_PASSWORD,MASTER_PORT,MASTER_PUBLIC_KEY_PATH,' + + 'MASTER_RETRY_COUNT,MASTER_SERVER_ID,MASTER_SSL,MASTER_SSL_CA,' + + 'MASTER_SSL_CAPATH,MASTER_SSL_CERT,MASTER_SSL_CIPHER,MASTER_SSL_CRL,' + + 'MASTER_SSL_CRLPATH,MASTER_SSL_KEY,MASTER_SSL_VERIFY_SERVER_CERT,' + + 'MASTER_TLS_CIPHERSUITES,MASTER_TLS_VERSION,MASTER_USER,' + + 'MASTER_ZSTD_COMPRESSION_LEVEL,MATCH,MAXVALUE,MAX_CONNECTIONS_PER_HOUR,' + + 'MAX_QUERIES_PER_HOUR,MAX_ROWS,MAX_SIZE,MAX_UPDATES_PER_HOUR,' + + 'MAX_USER_CONNECTIONS,MEDIUM,MEMBER,MESSAGE_TEXT,MIDDLEINT,MIGRATE,' + + 'MINUTE_MICROSECOND,MINUTE_SECOND,MIN_ROWS,MOD,MODE,MODIFIES,MODIFY,MUTEX,' + + 'MYSQL_ERRNO,NAME,NAMES,NATURAL,NCHAR,NESTED,NETWORK_NAMESPACE,NEVER,NEW,' + + 'NEXT,NO,NODEGROUP,NONE,NOT,NOWAIT,NO_WAIT,NO_WRITE_TO_BINLOG,NTH_VALUE,' + + 'NTILE,NULL,NULLS,NUMBER,NVARCHAR,OF,OFF,OFFSET,OJ,OLD,ON,ONE,ONLY,OPEN,' + + 'OPTIMIZE,OPTIMIZER_COSTS,OPTION,OPTIONAL,OPTIONALLY,OPTIONS,OR,ORDER,' + + 'ORDINALITY,ORGANIZATION,OTHERS,OUT,OUTER,OUTFILE,OVER,OWNER,PACK_KEYS,' + + 'PAGE,PARSER,PARSE_GCOL_EXPR,PARTIAL,PARTITION,PARTITIONING,PARTITIONS,' + + 'PASSWORD_LOCK_TIME,PATH,PERCENT_RANK,PERSIST,PERSIST_ONLY,PHASE,PLUGIN,' + + 'PLUGINS,PLUGIN_DIR,PORT,PRECEDES,PRECEDING,PREPARE,PRESERVE,PREV,PRIMARY,' + + 'PRIVILEGES,PRIVILEGE_CHECKS_USER,PROCEDURE,PROCESS,PROCESSLIST,PROFILE,' + + 'PROFILES,PROXY,PURGE,QUERY,QUICK,RANDOM,RANGE,RANK,READ,READS,READ_ONLY,' + + 'READ_WRITE,REBUILD,RECOVER,RECURSIVE,REDOFILE,REDO_BUFFER_SIZE,REFERENCE,' + + 'REFERENCES,REGEXP,REGISTRATION,RELAY,RELAYLOG,RELAY_LOG_FILE,' + + 'RELAY_LOG_POS,RELAY_THREAD,RELEASE,RELOAD,REMOTE,REMOVE,RENAME,REORGANIZE,' + + 'REPAIR,REPEATABLE,REPLACE,REPLICA,REPLICAS,REPLICATE_DO_DB,' + + 'REPLICATE_DO_TABLE,REPLICATE_IGNORE_DB,REPLICATE_IGNORE_TABLE,' + + 'REPLICATE_REWRITE_DB,REPLICATE_WILD_DO_TABLE,REPLICATE_WILD_IGNORE_TABLE,' + + 'REPLICATION,REQUIRE,REQUIRE_ROW_FORMAT,RESET,RESIGNAL,RESOURCE,RESPECT,' + + 'RESTART,RESTORE,RESTRICT,RESUME,RETAIN,RETURN,RETURNED_SQLSTATE,RETURNING,' + + 'RETURNS,REUSE,REVOKE,RLIKE,ROLE,ROLLBACK,ROLLUP,ROTATE,ROUTINE,ROW,ROWS,' + + 'ROW_FORMAT,ROW_NUMBER,RTREE,SAVEPOINT,SCHEDULE,SCHEMA,SCHEMAS,SCHEMA_NAME,' + + 'SECONDARY,SECONDARY_ENGINE,SECONDARY_ENGINE_ATTRIBUTE,SECONDARY_LOAD,' + + 'SECONDARY_UNLOAD,SECOND_MICROSECOND,SECURITY,SELECT,SENSITIVE,SEPARATOR,' + + 'SERIALIZABLE,SERVER,SESSION,SET,SHARE,SHOW,SHUTDOWN,SIGNAL,SIMPLE,SKIP,' + + 'SLAVE,SLOW,SNAPSHOT,SOCKET,SOME,SONAME,SOUNDS,SOURCE,SOURCE_AUTO_POSITION,' + + 'SOURCE_BIND,SOURCE_COMPRESSION_ALGORITHMS,SOURCE_CONNECT_RETRY,' + + 'SOURCE_DELAY,SOURCE_HEARTBEAT_PERIOD,SOURCE_HOST,SOURCE_LOG_FILE,' + + 'SOURCE_LOG_POS,SOURCE_PASSWORD,SOURCE_PORT,SOURCE_PUBLIC_KEY_PATH,' + + 'SOURCE_RETRY_COUNT,SOURCE_SSL,SOURCE_SSL_CA,SOURCE_SSL_CAPATH,' + + 'SOURCE_SSL_CERT,SOURCE_SSL_CIPHER,SOURCE_SSL_CRL,SOURCE_SSL_CRLPATH,' + + 'SOURCE_SSL_KEY,SOURCE_SSL_VERIFY_SERVER_CERT,SOURCE_TLS_CIPHERSUITES,' + + 'SOURCE_TLS_VERSION,SOURCE_USER,SOURCE_ZSTD_COMPRESSION_LEVEL,SPATIAL,' + + 'SPECIFIC,SQL,SQLEXCEPTION,SQLSTATE,SQLWARNING,SQL_AFTER_GTIDS,' + + 'SQL_AFTER_MTS_GAPS,SQL_BEFORE_GTIDS,SQL_BIG_RESULT,SQL_BUFFER_RESULT,' + + 'SQL_CACHE,SQL_CALC_FOUND_ROWS,SQL_NO_CACHE,SQL_SMALL_RESULT,SQL_THREAD,' + + 'SQL_TSI_DAY,SQL_TSI_HOUR,SQL_TSI_MINUTE,SQL_TSI_MONTH,SQL_TSI_QUARTER,' + + 'SQL_TSI_SECOND,SQL_TSI_WEEK,SQL_TSI_YEAR,SSL,STACKED,START,STARTING,' + + 'STARTS,STATS_AUTO_RECALC,STATS_PERSISTENT,STATS_SAMPLE_PAGES,STATUS,STOP,' + + 'STORAGE,STORED,STRAIGHT_JOIN,STREAM,SUBCLASS_ORIGIN,SUBJECT,SUBPARTITION,' + + 'SUBPARTITIONS,SUPER,SUSPEND,SWAPS,SWITCHES,SYSTEM,TABLE,TABLES,TABLESPACE,' + + 'TABLE_CHECKSUM,TABLE_NAME,TEMPORARY,TERMINATED,THAN,THREAD_PRIORITY,TIES,' + + 'TLS,TO,TRAILING,TRANSACTION,TRIGGER,TRIGGERS,TRUE,TYPE,TYPES,UNBOUNDED,' + + 'UNCOMMITTED,UNDO,UNDOFILE,UNDO_BUFFER_SIZE,UNINSTALL,UNION,UNIQUE,UNKNOWN,' + + 'UNLOCK,UNREGISTER,UPDATE,UPGRADE,URL,USAGE,USE,USER_RESOURCES,USE_FRM,' + + 'USING,VALIDATION,VALUE,VALUES,VARCHARACTER,VARIABLES,VARYING,VCPU,VIEW,' + + 'VIRTUAL,VISIBLE,WAIT,WARNINGS,WHERE,WINDOW,WITH,WITHOUT,WORK,WRAPPER,' + + 'WRITE,X509,XA,XID,XML,XOR,YEAR_MONTH,ZONE,' // SQL Plus commands: + 'CLOSE,CONDITION,CONTINUE,CURSOR,DECLARE,DO,EXIT,FETCH,FOUND,GOTO,' + 'HANDLER,ITERATE,LANGUAGE,LEAVE,LOOP,UNTIL,WHILE'; diff --git a/source/dbstructures.pas b/source/dbstructures.pas index fb031e322..da2902c71 100644 --- a/source/dbstructures.pas +++ b/source/dbstructures.pas @@ -6,20 +6,78 @@ interface uses - gnugettext, Vcl.Graphics, Winapi.Windows, System.SysUtils; + gnugettext, Vcl.Graphics, Winapi.Windows, System.SysUtils, System.Classes, System.IOUtils, + System.Generics.Collections, StrUtils; type + TNetType = ( + ntMySQL_TCPIP, + ntMySQL_NamedPipe, + ntMySQL_SSHtunnel, + ntMSSQL_NamedPipe, + ntMSSQL_TCPIP, + ntMSSQL_SPX, + ntMSSQL_VINES, + ntMSSQL_RPC, + ntPgSQL_TCPIP, + ntPgSQL_SSHtunnel, + ntSQLite, + ntMySQL_ProxySQLAdmin, + ntInterbase_TCPIP, + ntInterbase_Local, + ntFirebird_TCPIP, + ntFirebird_Local, + ntMySQL_RDS, + ntSQLiteEncrypted + ); + TNetTypeGroup = (ngMySQL, ngMSSQL, ngPgSQL, ngSQLite, ngInterbase); + TNetTypeLibs = TDictionary; + + // SQL query ids and provider + TStringMap = TDictionary; + TQueryId = (qDatabaseTable, qDatabaseTableId, qDatabaseDrop, + qDbObjectsTable, qDbObjectsCreateCol, qDbObjectsUpdateCol, qDbObjectsTypeCol, + qEmptyTable, qRenameTable, qRenameView, qCurrentUserHost, qLikeCompare, + qAddColumn, qChangeColumn, qRenameColumn, qForeignKeyEventAction, + qGlobalStatus, qCommandsCounters, qSessionVariables, qGlobalVariables, + qISSchemaCol, + qUSEQuery, qKillQuery, qKillProcess, + qFuncLength, qFuncCeil, qFuncLeft, qFuncNow, qFuncLastAutoIncNumber, + qLockedTables, qDisableForeignKeyChecks, qEnableForeignKeyChecks, + qOrderAsc, qOrderDesc, qGetRowCountExact, qGetRowCountApprox, + qForeignKeyDrop, qGetTableColumns, qGetCollations, qGetCollationsExtended, qGetCharsets, + qGetReverseForeignKeys, qExplain, qSetTimezone, + qShowFunctionStatus, qShowProcedureStatus, qShowTriggers, qShowEvents, qShowCreateTrigger, + qHelpKeyword, qShowWarnings, qGetEnumTypes, + qDropUser, qCreateRole, qDropRole, qReloadPrivileges, qGrantRole, qRevokeRole, qSetDefaultRole, + qAutoInc, qIndexVisible, qIndexInvisible, qGetAuthPlugins); + TSqlProvider = class + strict protected + FNetType: TNetType; + FServerVersion: Integer; + public + constructor Create(ANetType: TNetType); + function Has(AId: TQueryId): Boolean; + // Base version, just returns the original SQL string + function GetSql(AId: TQueryId): string; overload; virtual; + // Version for simple strings passed to Format() + function GetSql(AId: TQueryId; const Args: array of const): string; overload; + // Version for named parameters + function GetSql(AId: TQueryId; NamedParameters: TStringMap): string; overload; + property ServerVersion: Integer read FServerVersion write FServerVersion; + end; + // Column types TDBDatatypeIndex = (dbdtTinyint, dbdtSmallint, dbdtMediumint, dbdtInt, dbdtUint, dbdtBigint, dbdtSerial, dbdtBigSerial, dbdtFloat, dbdtDouble, dbdtDecimal, dbdtNumeric, dbdtReal, dbdtDoublePrecision, dbdtMoney, dbdtSmallmoney, dbdtDate, dbdtTime, dbdtYear, dbdtDatetime, dbdtDatetime2, dbdtDatetimeOffset, dbdtSmalldatetime, dbdtTimestamp, dbdtInterval, - dbdtChar, dbdtNchar, dbdtVarchar, dbdtNvarchar, dbdtTinytext, dbdtText, dbdtNtext, dbdtMediumtext, dbdtLongtext, + dbdtChar, dbdtNchar, dbdtVarchar, dbdtNvarchar, dbdtTinytext, dbdtText, dbdtCiText, dbdtNtext, dbdtMediumtext, dbdtLongtext, dbdtJson, dbdtJsonB, dbdtCidr, dbdtInet, dbdtMacaddr, - dbdtBinary, dbdtVarbinary, dbdtTinyblob, dbdtBlob, dbdtMediumblob, dbdtLongblob, dbdtImage, + dbdtBinary, dbdtVarbinary, dbdtTinyblob, dbdtBlob, dbdtMediumblob, dbdtLongblob, dbdtVector, dbdtImage, dbdtEnum, dbdtSet, dbdtBit, dbdtVarBit, dbdtBool, dbdtRegClass, dbdtRegProc, dbdtUnknown, - dbdtCursor, dbdtSqlvariant, dbdtTable, dbdtUniqueidentifier, dbdtHierarchyid, dbdtXML, + dbdtCursor, dbdtSqlvariant, dbdtTable, dbdtUniqueidentifier, dbdtInet4, dbdtInet6, dbdtHierarchyid, dbdtXML, dbdtPoint, dbdtLinestring, dbdtLineSegment, dbdtPolygon, dbdtGeometry, dbdtBox, dbdtPath, dbdtCircle, dbdtMultipoint, dbdtMultilinestring, dbdtMultipolygon, dbdtGeometrycollection ); @@ -45,6 +103,7 @@ TDBDatatype = record Format: String; // Used for date/time values when displaying and generating queries ValueMustMatch: String; Category: TDBDatatypeCategoryIndex; + MinVersion: Integer; end; // Column type category structure @@ -72,7 +131,7 @@ EDbError = class(Exception) public property ErrorCode: Cardinal read FErrorCode; property Hint: String read FHint; - constructor Create(const Msg: string; const ErrorCode: Cardinal=0; const Hint: String=''); + constructor Create(const Msg: string; const ErrorCode_: Cardinal=0; const Hint_: String=''); end; // DLL loading @@ -80,15 +139,15 @@ TDbLib = class(TObject) const LIB_PROC_ERROR: Cardinal = 1000; private - FDllFile: String; FHandle: HMODULE; protected + FDllFile: String; procedure AssignProc(var Proc: FARPROC; Name: PAnsiChar; Mandantory: Boolean=True); procedure AssignProcedures; virtual; abstract; public property Handle: HMODULE read FHandle; property DllFile: String read FDllFile; - constructor Create(DllFile, DefaultDll: String); + constructor Create(UsedDllFile, HintDefaultDll: String); virtual; destructor Destroy; override; end; @@ -134,13 +193,61 @@ implementation uses apphelpers; +{ TSqlProvider } + +constructor TSqlProvider.Create(ANetType: TNetType); +begin + FNetType := ANetType; + FServerVersion := 0; +end; + +function TSqlProvider.Has(AId: TQueryId): Boolean; +begin + Result := not GetSql(AId).IsEmpty; +end; + +function TSqlProvider.GetSql(AId: TQueryId): string; +begin + // Basic default SQL snippets compatible to all or most servers + case AId of + qEmptyTable: Result := 'DELETE FROM %s'; + qForeignKeyEventAction: Result := 'RESTRICT,CASCADE,SET NULL,NO ACTION'; + qOrderAsc: Result := 'ASC'; + qOrderDesc: Result := 'DESC'; + qGetRowCountExact: Result := 'SELECT COUNT(*) FROM :QuotedDbAndTableName'; + qAutoInc: Result := 'AUTO_INCREMENT'; + else Result := ''; + end; +end; + +function TSqlProvider.GetSql(AId: TQueryId; const Args: array of const): string; +begin + Result := GetSql(AId); + if Result.IsEmpty then + Exit; + Result := Format(Result, Args); +end; + +function TSqlProvider.GetSql(AId: TQueryId; NamedParameters: TStringMap): string; +var + Key: String; +begin + Result := GetSql(AId); + if Result.IsEmpty then + Exit; + for Key in NamedParameters.Keys do begin + Result := StringReplace(Result, ':'+Key, NamedParameters[Key], [rfReplaceAll]); + end; +end; + + { EDbError } -constructor EDbError.Create(const Msg: string; const ErrorCode: Cardinal=0; const Hint: String=''); +constructor EDbError.Create(const Msg: string; const ErrorCode_: Cardinal=0; const Hint_: String=''); begin - FErrorCode := ErrorCode; - FHint := Hint; + FErrorCode := ErrorCode_; + FHint := Hint_; inherited Create(Msg); end; @@ -148,19 +255,16 @@ constructor EDbError.Create(const Msg: string; const ErrorCode: Cardinal=0; cons { TDbLib } -constructor TDbLib.Create(DllFile, DefaultDll: String); +constructor TDbLib.Create(UsedDllFile, HintDefaultDll: String); var msg, ErrorHint: String; begin // Load DLL as is (with or without path) inherited Create; - FDllFile := DllFile; + FDllFile := UsedDllFile; + // On Windows, we have the full path to the dll file here, so even if the file portion is empty, FDllFile contains a path / non-empty string if not FileExists(FDllFile) then begin - msg := f_('File does not exist: %s', [FDllFile]) + - sLineBreak + sLineBreak + - f_('Please launch %s from the directory where you have installed it. Or just reinstall %s.', [APPNAME, APPNAME] - ); - Raise EdbError.Create(msg); + Raise EdbError.Create(_('No library selected. Please select one of the provided libraries in the drop-down.')); end; FHandle := LoadLibrary(PWideChar(FDllFile)); @@ -171,9 +275,9 @@ constructor TDbLib.Create(DllFile, DefaultDll: String); if GetLastError <> 0 then begin msg := msg + sLineBreak + sLineBreak + f_('Internal error %d: %s', [GetLastError, SysErrorMessage(GetLastError)]); end; - if (DefaultDll <> '') and (ExtractFileName(FDllFile) <> DefaultDll) then begin + if (HintDefaultDll <> '') and (ExtractFileName(FDllFile) <> HintDefaultDll) then begin ErrorHint := f_('You could try the default library %s in your session settings. (Current: %s)', - [DefaultDll, ExtractFileName(FDllFile)] + [HintDefaultDll, ExtractFileName(FDllFile)] ); end else begin ErrorHint := ''; diff --git a/source/dbstructures.postgresql.pas b/source/dbstructures.postgresql.pas index 37cf2f10d..207deb9e9 100644 --- a/source/dbstructures.postgresql.pas +++ b/source/dbstructures.postgresql.pas @@ -3,14 +3,14 @@ interface uses - dbstructures; + dbstructures, StrUtils; type // PostgreSQL structures TPQConnectStatus = (CONNECTION_OK, CONNECTION_BAD, CONNECTION_STARTED, CONNECTION_MADE, CONNECTION_AWAITING_RESPONSE, CONNECTION_AUTH_OK, CONNECTION_SETENV, CONNECTION_SSL_STARTUP, CONNECTION_NEEDED); PPGconn = Pointer; PPGresult = Pointer; - POid = Cardinal; + POid = Cardinal; // Object ID is a fundamental type in Postgres. TPostgreSQLLib = class(TDbLib) PQconnectdb: function(const ConnInfo: PAnsiChar): PPGconn cdecl; PQerrorMessage: function(const Handle: PPGconn): PAnsiChar cdecl; @@ -36,8 +36,15 @@ TPostgreSQLLib = class(TDbLib) procedure AssignProcedures; override; end; + TPostgreSQLProvider = class(TSqlProvider) + public + function GetSql(AId: TQueryId): string; override; + end; + +const InvalidOid: POid = 0; + var - PostgreSQLDatatypes: Array[0..37] of TDBDatatype = + PostgreSQLDatatypes: Array[0..39] of TDBDatatype = ( ( Index: dbdtUnknown; @@ -183,18 +190,6 @@ TPostgreSQLLib = class(TDbLib) LoadPart: False; Category: dtcReal; ), - ( - Index: dbdtMoney; - NativeTypes: '790'; - Name: 'MONEY'; - Description: 'Currency amount. Range: -92233720368547758.08 to +92233720368547758.07. Storage Size: 8 Bytes.'; - HasLength: True; - RequiresLength: False; - HasBinary: False; - HasDefault: False; - LoadPart: False; - Category: dtcReal; - ), ( Index: dbdtChar; NativeTypes: '18|1042'; @@ -234,6 +229,19 @@ TPostgreSQLLib = class(TDbLib) LoadPart: True; Category: dtcText; ), + ( + Index: dbdtCiText; + NativeTypes: '?'; + Name: 'CITEXT'; + Names: 'citext'; + Description: 'A case-insensitive character string type. Essentially, it internally calls lower when comparing values. Otherwise, it behaves almost exactly like text.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: True; + Category: dtcText; + ), ( Index: dbdtCidr; NativeTypes: '650'; @@ -273,6 +281,18 @@ TPostgreSQLLib = class(TDbLib) LoadPart: False; Category: dtcText; ), + ( + Index: dbdtMoney; + NativeTypes: '790'; + Name: 'MONEY'; + Description: 'Currency amount. Range: -92233720368547758.08 to +92233720368547758.07. Storage Size: 8 Bytes.'; + HasLength: True; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcText; + ), ( Index: dbdtDate; NativeTypes: '1082'; @@ -489,6 +509,19 @@ TPostgreSQLLib = class(TDbLib) LoadPart: False; Category: dtcOther; ), + ( + Index: dbdtEnum; + NativeTypes: 'e'; + Name: 'ENUM'; + Names: ''; + Description: 'A list of quoted labels, each of which must be less than NAMEDATALEN bytes long (64 bytes in a standard PostgreSQL build)'; + HasLength: True; // Enables the Length/set field in table editor + RequiresLength: True; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcOther; + ), ( Index: dbdtJson; NativeTypes: '114'; @@ -558,4 +591,161 @@ procedure TPostgreSQLLib.AssignProcedures; end; +{ TPostgreSQLProvider } + +function TPostgreSQLProvider.GetSql(AId: TQueryId): string; +begin + case AId of + qDatabaseDrop: Result := 'DROP SCHEMA %s'; + qRenameTable: Result := 'ALTER TABLE %s RENAME TO %s'; + qRenameView: Result := 'ALTER VIEW %s RENAME TO %s'; + qCurrentUserHost: Result := 'SELECT CURRENT_USER'; + qLikeCompare: Result := '%s ILIKE %s'; + qAddColumn: Result := 'ADD %s'; + qChangeColumn: Result := 'ALTER COLUMN %s %s'; + qRenameColumn: Result := 'RENAME COLUMN %s TO %s'; + qForeignKeyEventAction: Result := 'RESTRICT,CASCADE,SET NULL,NO ACTION,SET DEFAULT'; + qSessionVariables: Result := 'SHOW ALL'; + qGlobalVariables: Result := 'SHOW ALL'; + qISSchemaCol: Result := '%s_schema'; + qUSEQuery: Result := 'SET search_path TO %s'; + qKillQuery: Result := 'SELECT pg_cancel_backend(%d)'; + qKillProcess: Result := 'SELECT pg_cancel_backend(%d)'; + qFuncLength: Result := 'LENGTH'; + qFuncCeil: Result := 'CEIL'; + qFuncLeft: Result := 'SUBSTRING(%s, 1, %d)'; + qFuncNow: Result := 'NOW()'; + qFuncLastAutoIncNumber: Result := 'LASTVAL()'; + qLockedTables: Result := ''; + qDisableForeignKeyChecks: Result := ''; + qEnableForeignKeyChecks: Result := ''; + qForeignKeyDrop: Result := 'DROP CONSTRAINT %s'; + + // This uses pg_attribute.attgenerated, which only exists starting in PostgreSQL 12 + qGetTableColumns: Result := IfThen( + FServerVersion >= 120000, + 'SELECT ' + + ' n.nspname AS table_schema, ' + + ' c.relname AS table_name, ' + + ' a.attname AS column_name, ' + + ' a.attnum AS ordinal_position, ' + + ' pg_catalog.format_type(a.atttypid, a.atttypmod) AS data_type, ' + + // YES/NO like information_schema.is_nullable + ' CASE ' + + ' WHEN a.attnotnull THEN ''NO'' ' + + ' ELSE ''YES'' ' + + ' END AS is_nullable, ' + + // Character maximum length (in characters) + ' CASE ' + + ' WHEN (bt.typcategory = ''S'' OR (bt.oid IS NULL AND t.typcategory = ''S'')) ' + + ' AND a.atttypmod <> -1 ' + + ' THEN a.atttypmod - 4 ' + + ' ELSE NULL ' + + ' END AS character_maximum_length, ' + + // Numeric precision / scale (NULL for non-numeric) + ' CASE ' + + ' WHEN (bt.typcategory IN (''N'',''F'')) OR (bt.oid IS NULL AND t.typcategory IN (''N'',''F'')) ' + + ' THEN ' + + ' CASE ' + + ' WHEN a.atttypmod = -1 THEN NULL ' + + ' ELSE ((a.atttypmod - 4) >> 16)::integer ' + + ' END ' + + ' END AS numeric_precision, ' + + ' CASE ' + + ' WHEN (bt.typcategory IN (''N'',''F'')) OR (bt.oid IS NULL AND t.typcategory IN (''N'',''F'')) ' + + ' THEN ' + + ' CASE ' + + ' WHEN a.atttypmod = -1 THEN NULL ' + + ' ELSE ((a.atttypmod - 4) & 65535)::integer ' + + ' END ' + + ' END AS numeric_scale, ' + + // Datetime precision (for time/timestamp/interval) + ' CASE ' + + ' WHEN (bt.typcategory = ''D'' OR (bt.oid IS NULL AND t.typcategory = ''D'')) ' + + ' AND a.atttypmod <> -1 ' + + ' THEN a.atttypmod ' + + ' ELSE NULL ' + + ' END AS datetime_precision, ' + + // Character set name: PostgreSQL has one per DB; mimic information_schema + ' CASE ' + + ' WHEN (bt.typcategory = ''S'' OR (bt.oid IS NULL AND t.typcategory = ''S'')) ' + + ' THEN current_database() ' + + ' ELSE NULL ' + + ' END AS character_set_name, ' + + // Collation name for collatable columns + ' CASE ' + + ' WHEN (bt.typcategory = ''S'' OR (bt.oid IS NULL AND t.typcategory = ''S'')) ' + + ' THEN ' + + ' CASE ' + + ' WHEN a.attcollation <> t.typcollation ' + + ' THEN coll.collname ' + + ' ELSE NULL ' + + ' END ' + + ' ELSE NULL ' + + ' END AS collation_name, ' + + // Default expression for non-generated columns + ' CASE ' + + ' WHEN a.attgenerated = '''' AND a.atthasdef ' + + ' THEN pg_get_expr(ad.adbin, ad.adrelid) ' + + ' ELSE NULL ' + + ' END AS column_default, ' + + // Generation expression for generated columns + ' CASE ' + + ' WHEN a.attgenerated <> '''' AND a.atthasdef ' + + ' THEN pg_get_expr(ad.adbin, ad.adrelid) ' + + ' ELSE NULL ' + + ' END AS generation_expression, ' + + ' d.description AS column_comment ' + + 'FROM pg_catalog.pg_class AS c ' + + 'JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace ' + + 'JOIN pg_catalog.pg_attribute AS a ON a.attrelid = c.oid ' + + 'JOIN pg_catalog.pg_type AS t ON t.oid = a.atttypid ' + + 'LEFT JOIN pg_catalog.pg_type AS bt ON bt.oid = t.typbasetype ' + + 'LEFT JOIN pg_catalog.pg_attrdef AS ad ' + + ' ON ad.adrelid = a.attrelid ' + + ' AND ad.adnum = a.attnum ' + + 'LEFT JOIN pg_catalog.pg_description AS d ' + + ' ON d.objoid = a.attrelid ' + + ' AND d.objsubid = a.attnum ' + + 'LEFT JOIN pg_catalog.pg_collation AS coll ' + + ' ON coll.oid = a.attcollation ' + + 'WHERE n.nspname = %s ' + + ' AND a.attnum > 0 ' + + ' AND NOT a.attisdropped ' + + ' AND c.relname = %s ' + + 'ORDER BY ordinal_position', + '' // ServerVersion < 12 + ); + + qGetCharsets: Result := 'SELECT DISTINCT pg_encoding_to_char(enc) AS "Charset" FROM '+ + '(SELECT conforencoding AS enc FROM pg_catalog.pg_conversion '+ + ' UNION '+ + ' SELECT contoencoding AS enc FROM pg_catalog.pg_conversion) AS x'; + qGetRowCountApprox: Result := 'SELECT reltuples::bigint FROM pg_class'+ + ' LEFT JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace'+ + ' WHERE pg_class.relkind=''r'''+ + ' AND pg_namespace.nspname=:EscapedDatabase'+ + ' AND pg_class.relname=:EscapedName'; + qGetEnumTypes: Result := IfThen( + FServerVersion >= 90000, + 'SELECT ' + + ' n.nspname AS enum_schema, ' + + ' t.typname AS enum_name, ' + + ' string_agg(e.enumlabel, ''|'' ORDER BY e.enumsortorder) AS enum_labels ' + + 'FROM pg_type AS t ' + + 'JOIN pg_enum AS e ' + + ' ON t.oid = e.enumtypid ' + + 'JOIN pg_namespace AS n ' + + ' ON n.oid = t.typnamespace ' + + 'WHERE t.typtype = ''e'' ' + + 'GROUP BY n.nspname, t.typname ' + + 'ORDER BY UPPER(t.typname)', + '' // ServerVersion < 9 + ); + qAutoInc: Result := 'SERIAL'; + else Result := inherited; + end; +end; + + end. diff --git a/source/dbstructures.sqlite.pas b/source/dbstructures.sqlite.pas index 5267935c9..f34faa97e 100644 --- a/source/dbstructures.sqlite.pas +++ b/source/dbstructures.sqlite.pas @@ -136,8 +136,25 @@ TSQLiteLib = class(TDbLib) ): Integer; cdecl; sqlite3_collation_needed: function(ppDb: Psqlite3; userData: Pointer; Func: TSQLiteCollationNeededCallback): Integer; cdecl; sqlite3_create_collation: function(ppDb: Psqlite3; const zName: PAnsiChar; eTextRep: Integer; pArg: Pointer; xCompare: TSQLiteCollation): Integer; cdecl; + // Additionally, for use in Multiple Ciphers library: + sqlite3_key: function(ppDb: Psqlite3; const pKey: Pointer; nKey: Integer): Integer; cdecl; + sqlite3mc_cipher_count: function(): Integer; cdecl; + sqlite3mc_cipher_name: function(cipherIndex: Integer): PAnsiChar; cdecl; + sqlite3mc_cipher_index: function(const cipherName: PAnsiChar): Integer; cdecl; + sqlite3mc_config: function(ppDb: Psqlite3; const paramName: PAnsiChar; newValue: Integer): Integer; cdecl; + sqlite3mc_config_cipher: function(ppDb: Psqlite3; const cipherName: PAnsiChar; const paramName: PAnsiChar; newValue: Integer): Integer; cdecl; + private + FWithMultipleCipherFunctions: Boolean; protected procedure AssignProcedures; override; + public + constructor Create(DllFile, DefaultDll: String); override; + constructor CreateWithMultipleCipherFunctions(DllFile, DefaultDll: String); + end; + + TSQLiteProvider = class(TSqlProvider) + public + function GetSql(AId: TQueryId): string; override; end; var @@ -322,6 +339,20 @@ TSQLiteLib = class(TDbLib) implementation + +constructor TSQLiteLib.Create(DllFile, DefaultDll: String); +begin + FWithMultipleCipherFunctions := False; + inherited; +end; + +constructor TSQLiteLib.CreateWithMultipleCipherFunctions(DllFile, DefaultDll: String); +begin + FWithMultipleCipherFunctions := True; + inherited Create(DllFile, DefaultDll); +end; + + procedure TSQLiteLib.AssignProcedures; begin AssignProc(@sqlite3_open, 'sqlite3_open'); @@ -350,6 +381,55 @@ procedure TSQLiteLib.AssignProcedures; AssignProc(@sqlite3_table_column_metadata, 'sqlite3_table_column_metadata'); AssignProc(@sqlite3_collation_needed, 'sqlite3_collation_needed'); AssignProc(@sqlite3_create_collation, 'sqlite3_create_collation'); + if FWithMultipleCipherFunctions then begin + // Additionally, for use in Multiple Ciphers library: + AssignProc(@sqlite3_key, 'sqlite3_key', False); + AssignProc(@sqlite3mc_cipher_count, 'sqlite3mc_cipher_count'); + AssignProc(@sqlite3mc_cipher_name, 'sqlite3mc_cipher_name'); + AssignProc(@sqlite3mc_cipher_index, 'sqlite3mc_cipher_index'); + AssignProc(@sqlite3mc_config, 'sqlite3mc_config'); + AssignProc(@sqlite3mc_config_cipher, 'sqlite3mc_config_cipher'); + end; end; + +{ TSQLiteProvider } + +function TSQLiteProvider.GetSql(AId: TQueryId): string; +begin + case AId of + qDatabaseDrop: Result := 'DROP DATABASE %s'; + qRenameTable: Result := 'ALTER TABLE %s RENAME TO %s'; + qRenameView: Result := 'ALTER TABLE %s RENAME TO %s'; + qCurrentUserHost: Result := ''; // unsupported + qLikeCompare: Result := '%s LIKE %s'; + qAddColumn: Result := 'ADD COLUMN %s'; + qChangeColumn: Result := ''; // SQLite only supports renaming + qRenameColumn: Result := 'RENAME COLUMN %s TO %s'; + qSessionVariables: Result := 'SELECT null, null'; // Todo: combine "PRAGMA pragma_list" + "PRAGMA a; PRAGMY b; ..."? + qGlobalVariables: Result := 'SHOW GLOBAL VARIABLES'; + qISSchemaCol: Result := '%s_SCHEMA'; + qUSEQuery: Result := ''; + qKillQuery: Result := 'KILL %d'; + qKillProcess: Result := 'KILL %d'; + qFuncLength: Result := 'LENGTH'; + qFuncCeil: Result := 'CEIL'; + qFuncLeft: Result := 'SUBSTR(%s, 1, %d)'; + qFuncNow: Result := 'DATETIME()'; + qFuncLastAutoIncNumber: Result := 'LAST_INSERT_ID()'; + qLockedTables: Result := ''; + qDisableForeignKeyChecks: Result := ''; + qEnableForeignKeyChecks: Result := ''; + qForeignKeyDrop: Result := 'DROP FOREIGN KEY %s'; + qGetTableColumns: Result := 'SELECT * FROM pragma_table_xinfo(%s, %s)'; + // See https://www.sqlite.org/datatype3.html#collation_sequence_examples + qGetCollations: Result := 'SELECT name AS "Collation", '''' AS "Charset", '''' AS "Id", '''' AS "Default", '''' AS "Compiled", ''1'' AS Sortlen from pragma_collation_list'; + qGetCharsets: Result := 'SELECT ''UTF-8'' AS "Charset", ''UTF-8'' AS "Description" '+ + 'UNION SELECT ''UTF-16le'', ''UTF-16 Little Endian'' '+ + 'UNION SELECT ''UTF-16be'', ''UTF-16 Big Endian'''; + else Result := inherited; + end; +end; + + end. \ No newline at end of file diff --git a/source/editvar.pas b/source/editvar.pas index d522fa1cb..9d1a7b2ee 100644 --- a/source/editvar.pas +++ b/source/editvar.pas @@ -158,6 +158,7 @@ procedure TfrmEditVariable.FormShow(Sender: TObject); procedure TfrmEditVariable.btnOKClick(Sender: TObject); var sql, val: String; + Conn: TDBConnection; begin // Syntax taken from http://dev.mysql.com/doc/refman/4.1/en/using-system-variables.html sql := 'SET @@'; @@ -167,6 +168,8 @@ procedure TfrmEditVariable.btnOKClick(Sender: TObject); sql := sql + 'global'; sql := sql + '.' + FVar.Name + ' = '; + Conn := MainForm.ActiveConnection; + case FVarType of vtNumeric: val := IntToStr(UpDownNumber.Position); vtString: begin @@ -176,16 +179,17 @@ procedure TfrmEditVariable.btnOKClick(Sender: TObject); if ExecRegExpr('^\d+(\.\d*)?$', FVarValue) then val := editString.Text else - val := MainForm.ActiveConnection.EscapeString(editString.Text); + val := Conn.EscapeString(editString.Text); end; vtBoolean: val := IntToStr(Integer(radioBooleanOn.Checked)); - vtEnum: val := MainForm.ActiveConnection.EscapeString(comboEnum.Text); + vtEnum: val := Conn.EscapeString(comboEnum.Text); end; sql := sql + val; // Set the value and keep the form open in any error case try - MainForm.ActiveConnection.Query(sql); + Conn.Query(sql); + Conn.ShowWarnings; except on E:EDbError do begin ModalResult := mrNone; diff --git a/source/event_editor.pas b/source/event_editor.pas index c8940d750..9fc4d2ef0 100644 --- a/source/event_editor.pas +++ b/source/event_editor.pas @@ -231,6 +231,7 @@ function TfrmEventEditor.ApplyModifications: TModalResult; sql := ComposeAlterStatement; try MainForm.ActiveConnection.Query(sql); + MainForm.ActiveConnection.ShowWarnings; DBObject.Name := editName.Text; DBObject.UnloadDetails; tabALTERcode.TabVisible := ObjectExists; @@ -272,7 +273,7 @@ function TfrmEventEditor.ComposeStatement(CreateOrAlter, ObjName: String): Strin Result := CreateOrAlter + ' '; if comboDefiner.Text <> '' then Result := Result + 'DEFINER='+DBObject.Connection.QuoteIdent(comboDefiner.Text, True, '@')+' '; - Result := Result + 'EVENT ' + DBObject.Connection.QuoteIdent(ObjName) + CRLF + #9 + 'ON SCHEDULE' + CRLF + #9#9; + Result := Result + 'EVENT ' + DBObject.Connection.QuoteIdent(ObjName) + sLineBreak + CodeIndent + 'ON SCHEDULE' + sLineBreak + CodeIndent(2); if radioOnce.Checked then begin d := dateOnce.DateTime; ReplaceTime(d, timeOnce.DateTime); @@ -297,14 +298,14 @@ function TfrmEventEditor.ComposeStatement(CreateOrAlter, ObjName: String): Strin end; if chkDropAfterExpiration.Checked then - Result := Result + #9 + 'ON COMPLETION NOT PRESERVE' + Result := Result + CodeIndent + 'ON COMPLETION NOT PRESERVE' else - Result := Result + #9 + 'ON COMPLETION PRESERVE'; + Result := Result + CodeIndent + 'ON COMPLETION PRESERVE'; if ObjectExists and (DBObject.Name <> editName.Text) then - Result := Result + CRLF + #9 + 'RENAME TO ' + DBObject.Connection.QuoteIdent(editName.Text); - Result := Result + CRLF + #9 + UpperCase(grpState.Items[grpState.ItemIndex]); - Result := Result + CRLF + #9 + 'COMMENT ' + DBObject.Connection.EscapeString(editComment.Text); - Result := Result + CRLF + #9 + 'DO ' + SynMemoBody.Text; + Result := Result + sLineBreak + CodeIndent + 'RENAME TO ' + DBObject.Connection.QuoteIdent(editName.Text); + Result := Result + sLineBreak + CodeIndent + UpperCase(grpState.Items[grpState.ItemIndex]); + Result := Result + sLineBreak + CodeIndent + 'COMMENT ' + DBObject.Connection.EscapeString(editComment.Text); + Result := Result + sLineBreak + CodeIndent + 'DO ' + SynMemoBody.Text; end; diff --git a/source/exportgrid.dfm b/source/exportgrid.dfm index 03aaff976..efbaf7bcf 100644 --- a/source/exportgrid.dfm +++ b/source/exportgrid.dfm @@ -3,11 +3,11 @@ object frmExportGrid: TfrmExportGrid Top = 0 BorderIcons = [biSystemMenu] Caption = 'Export grid rows' - ClientHeight = 445 - ClientWidth = 373 + ClientHeight = 412 + ClientWidth = 574 Color = clBtnFace - Constraints.MinHeight = 480 - Constraints.MinWidth = 350 + Constraints.MinHeight = 450 + Constraints.MinWidth = 530 Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -12 @@ -16,91 +16,69 @@ object frmExportGrid: TfrmExportGrid Position = poMainFormCenter OnClose = FormClose OnCreate = FormCreate - OnResize = FormResize OnShow = FormShow DesignSize = ( - 373 - 445) + 574 + 412) TextHeight = 14 object btnOK: TButton - Left = 209 - Top = 412 + Left = 410 + Top = 379 Width = 75 Height = 25 Anchors = [akRight, akBottom] Caption = 'OK' Default = True ModalResult = 1 - TabOrder = 0 + TabOrder = 5 OnClick = btnOKClick end object btnCancel: TButton - Left = 290 - Top = 412 + Left = 491 + Top = 379 Width = 75 Height = 25 Anchors = [akRight, akBottom] Cancel = True Caption = 'Cancel' ModalResult = 2 - TabOrder = 1 - end - object grpFormat: TRadioGroup - Left = 8 - Top = 112 - Width = 137 - Height = 294 - Anchors = [akLeft, akTop, akBottom] - Caption = 'Output format' - ItemIndex = 0 - Items.Strings = ( - 'Excel compatible' - 'Delimited text' - 'HTML table' - 'XML' - 'SQL INSERTs' - 'SQL REPLACEs' - 'LaTeX' - 'Wiki markup' - 'PHP Array') - TabOrder = 2 - OnClick = grpFormatClick + TabOrder = 6 end object grpSelection: TRadioGroup - Left = 151 - Top = 112 - Width = 214 + Left = 8 + Top = 168 + Width = 558 Height = 66 - Anchors = [akTop, akRight] + Anchors = [akLeft, akTop, akRight] Caption = 'Row selection' ItemIndex = 1 Items.Strings = ( 'Selected rows' 'All loaded rows') - TabOrder = 3 + TabOrder = 2 end object grpOutput: TGroupBox Left = 8 Top = 8 - Width = 357 + Width = 558 Height = 98 Anchors = [akLeft, akTop, akRight] Caption = 'Output target' - TabOrder = 4 + TabOrder = 0 DesignSize = ( - 357 + 558 98) object lblEncoding: TLabel Left = 8 Top = 72 - Width = 47 - Height = 13 + Width = 54 + Height = 14 Caption = 'Encoding:' end object radioOutputCopyToClipboard: TRadioButton Left = 8 Top = 18 - Width = 335 + Width = 540 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Copy to clipboard' @@ -121,8 +99,8 @@ object frmExportGrid: TfrmExportGrid object editFilename: TButtonedEdit Left = 79 Top = 42 - Width = 264 - Height = 21 + Width = 469 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain LeftButton.DropDownMenu = popupRecentFiles @@ -139,152 +117,164 @@ object frmExportGrid: TfrmExportGrid object comboEncoding: TComboBox Left = 79 Top = 69 - Width = 264 - Height = 21 + Width = 469 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] TabOrder = 3 end end object grpOptions: TGroupBox - Left = 151 - Top = 184 - Width = 214 - Height = 222 - Anchors = [akTop, akRight, akBottom] + Left = 8 + Top = 240 + Width = 558 + Height = 133 + Anchors = [akLeft, akTop, akRight, akBottom] Caption = 'Options' - TabOrder = 5 + TabOrder = 3 DesignSize = ( - 214 - 222) + 558 + 133) object lblSeparator: TLabel - Left = 6 - Top = 116 - Width = 76 - Height = 13 + Left = 279 + Top = 18 + Width = 83 + Height = 14 Caption = 'Field separator:' end object lblEncloser: TLabel - Left = 6 - Top = 141 - Width = 44 - Height = 13 + Left = 279 + Top = 44 + Width = 49 + Height = 14 Caption = 'Encloser:' end object lblTerminator: TLabel - Left = 6 - Top = 167 - Width = 76 - Height = 13 + Left = 279 + Top = 71 + Width = 87 + Height = 14 Caption = 'Line terminator:' end object lblNull: TLabel - Left = 6 - Top = 194 - Width = 57 - Height = 13 + Left = 279 + Top = 97 + Width = 64 + Height = 14 Caption = 'NULL value:' end object chkIncludeColumnNames: TCheckBox - Left = 6 + Left = 8 Top = 18 - Width = 191 + Width = 257 Height = 17 - Anchors = [akLeft, akTop, akRight] Caption = 'Include column names' Checked = True State = cbChecked TabOrder = 0 end object editSeparator: TButtonedEdit - Left = 106 - Top = 112 - Width = 93 - Height = 21 + Left = 400 + Top = 15 + Width = 148 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.DisabledImageIndex = 107 RightButton.ImageIndex = 108 RightButton.Visible = True - TabOrder = 3 + TabOrder = 6 Text = ';' OnChange = editCSVChange OnRightButtonClick = editCSVRightButtonClick end object editEncloser: TButtonedEdit - Left = 106 - Top = 138 - Width = 93 - Height = 21 + Left = 400 + Top = 41 + Width = 148 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.DisabledImageIndex = 107 RightButton.ImageIndex = 108 RightButton.Visible = True - TabOrder = 4 + TabOrder = 7 OnChange = editCSVChange OnRightButtonClick = editCSVRightButtonClick end object editTerminator: TButtonedEdit - Left = 106 - Top = 164 - Width = 93 - Height = 21 + Left = 400 + Top = 68 + Width = 148 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.DisabledImageIndex = 107 RightButton.ImageIndex = 108 RightButton.Visible = True - TabOrder = 5 + TabOrder = 8 Text = '\r\n' OnChange = editCSVChange OnRightButtonClick = editCSVRightButtonClick end object chkIncludeAutoIncrement: TCheckBox - Left = 6 + Left = 8 Top = 41 - Width = 191 + Width = 257 Height = 17 - Anchors = [akLeft, akTop, akRight] Caption = 'Include auto increment column' TabOrder = 1 end object chkIncludeQuery: TCheckBox - Left = 6 - Top = 64 - Width = 191 + Left = 8 + Top = 87 + Width = 257 Height = 17 - Anchors = [akLeft, akTop, akRight] Caption = 'Include SQL query' - TabOrder = 2 + TabOrder = 3 end object editNull: TButtonedEdit - Left = 106 - Top = 191 - Width = 93 - Height = 21 + Left = 400 + Top = 94 + Width = 148 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.DisabledImageIndex = 107 RightButton.ImageIndex = 108 RightButton.Visible = True - TabOrder = 6 + TabOrder = 9 OnChange = editCSVChange OnRightButtonClick = editCSVRightButtonClick end object chkRemoveLinebreaks: TCheckBox - Left = 6 - Top = 87 - Width = 191 + Left = 8 + Top = 110 + Width = 257 Height = 17 - Anchors = [akLeft, akTop, akRight] Caption = 'Remove linebreaks from contents' - TabOrder = 7 + TabOrder = 4 + end + object chkOpenFile: TCheckBox + Left = 8 + Top = 133 + Width = 257 + Height = 17 + Caption = 'Open file after creation' + TabOrder = 5 + end + object chkFocusedColumnOnly: TCheckBox + Left = 8 + Top = 64 + Width = 265 + Height = 17 + Caption = 'Only focused column (%s)' + TabOrder = 2 end end object btnSetClipboardDefaults: TButton Left = 8 - Top = 412 + Top = 379 Width = 153 Height = 25 Anchors = [akLeft, akBottom] @@ -292,12 +282,50 @@ object frmExportGrid: TfrmExportGrid ImageIndex = 4 ImageName = 'icons8-paste-100' Images = MainForm.VirtualImageListMain - TabOrder = 6 + TabOrder = 4 OnClick = btnSetClipboardDefaultsClick end + object grpFormat: TGroupBox + Left = 8 + Top = 112 + Width = 558 + Height = 50 + Anchors = [akLeft, akTop, akRight] + Caption = 'Output format' + TabOrder = 1 + DesignSize = ( + 558 + 50) + object comboFormat: TComboBoxEx + Left = 8 + Top = 18 + Width = 540 + Height = 23 + ItemsEx = < + item + Caption = 'Excel CSV' + ImageIndex = 49 + SelectedImageIndex = 49 + end + item + Caption = 'CSV' + ImageIndex = 50 + SelectedImageIndex = 50 + end + item + Caption = '...' + end> + Style = csExDropDownList + Anchors = [akLeft, akTop, akRight] + TabOrder = 0 + OnSelect = comboFormatSelect + Images = MainForm.VirtualImageListMain + DropDownCount = 20 + end + end object popupCSVchar: TPopupMenu AutoHotkeys = maManual - Left = 224 + Left = 160 Top = 6 object menuCSVtab: TMenuItem Caption = 'Tab' @@ -367,7 +395,7 @@ object frmExportGrid: TfrmExportGrid object popupRecentFiles: TPopupMenu AutoHotkeys = maManual OnPopup = popupRecentFilesPopup - Left = 312 + Left = 248 Top = 6 end end diff --git a/source/exportgrid.pas b/source/exportgrid.pas index 863a37758..ff2ff207b 100644 --- a/source/exportgrid.pas +++ b/source/exportgrid.pas @@ -5,7 +5,7 @@ interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Menus, Vcl.ComCtrls, VirtualTrees, SynExportHTML, gnugettext, Vcl.ActnList, - extra_controls, dbstructures, SynRegExpr, System.StrUtils, System.IOUtils; + extra_controls, dbstructures, SynRegExpr, System.StrUtils, System.IOUtils, VirtualTrees.BaseTree, VirtualTrees.Types; type TGridExportFormat = ( @@ -23,13 +23,14 @@ interface efJiraTextile, efPHPArray, efMarkDown, - efJSON + efJSON, + efJSONLines ); TfrmExportGrid = class(TExtForm) btnOK: TButton; btnCancel: TButton; - grpFormat: TRadioGroup; + chkFocusedColumnOnly: TCheckBox; grpSelection: TRadioGroup; grpOutput: TGroupBox; radioOutputCopyToClipboard: TRadioButton; @@ -67,6 +68,9 @@ TfrmExportGrid = class(TExtForm) editNull: TButtonedEdit; btnSetClipboardDefaults: TButton; chkRemoveLinebreaks: TCheckBox; + grpFormat: TGroupBox; + comboFormat: TComboBoxEx; + chkOpenFile: TCheckBox; procedure FormCreate(Sender: TObject); procedure CalcSize(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); @@ -79,9 +83,8 @@ TfrmExportGrid = class(TExtForm) procedure ValidateControls(Sender: TObject); procedure btnOKClick(Sender: TObject); procedure FormShow(Sender: TObject); - procedure grpFormatClick(Sender: TObject); + procedure comboFormatSelect(Sender: TObject); procedure btnSetClipboardDefaultsClick(Sender: TObject); - procedure FormResize(Sender: TObject); private { Private declarations } FCSVEditor: TButtonedEdit; @@ -95,7 +98,8 @@ TfrmExportGrid = class(TExtForm) procedure SetExportFormatByFilename; procedure SelectRecentFile(Sender: TObject); procedure PutFilenamePlaceholder(Sender: TObject); - function FormatExcelCsv(Text, Encloser: String; DataType: TDBDatatype): String; + function FormatCsv(Text, Encloser: String; DataType: TDBDatatype; SubFormat: TGridExportFormat): String; + function FormatJson(Text: String): String; function FormatPhp(Text: String): String; function FormatLatex(Text: String): String; public @@ -116,7 +120,8 @@ TfrmExportGrid = class(TExtForm) ('jira-textile'), ('php'), ('md'), - ('json') + ('json'), + ('jsonl') ); const FormatToDescription: Array[TGridExportFormat] of String = ( @@ -134,7 +139,8 @@ TfrmExportGrid = class(TExtForm) ('Jira Textile'), ('PHP Array'), ('Markdown Here'), - ('JSON') + ('JSON'), + ('JSON Lines') ); const FormatToImageIndex: Array[TGridExportFormat] of Integer = ( @@ -152,7 +158,8 @@ TfrmExportGrid = class(TExtForm) 154, // Jira 202, // PHP 199, // Markdown - 200 // JSON + 200, // JSON + 200 // JSON Lines ); const CopyAsActionPrefix = 'actCopyAs'; property Grid: TVirtualStringTree read FGrid write FGrid; @@ -170,8 +177,9 @@ implementation procedure TfrmExportGrid.FormCreate(Sender: TObject); var - FormatDesc: String; + ef: TGridExportFormat; SenderName: String; + comboItem: TComboExItem; begin HasSizeGrip := True; editFilename.Text := AppSettings.ReadString(asGridExportFilename); @@ -179,20 +187,25 @@ procedure TfrmExportGrid.FormCreate(Sender: TObject); comboEncoding.Items.Assign(MainForm.FileEncodings); comboEncoding.Items.Delete(0); // Remove "Auto detect" comboEncoding.ItemIndex := AppSettings.ReadInt(asGridExportEncoding); - grpFormat.Items.Clear; - for FormatDesc in FormatToDescription do - grpFormat.Items.Add(FormatDesc); + comboFormat.Items.Clear; + for ef:=Low(TGridExportFormat) to High(TGridExportFormat) do begin + comboItem := TComboExItem.Create(comboFormat.ItemsEx); + comboItem.Caption := FormatToDescription[ef]; + comboItem.ImageIndex := FormatToImageIndex[ef]; + end; SenderName := Owner.Name; FHiddenCopyMode := SenderName.StartsWith(CopyAsActionPrefix); if FHiddenCopyMode then begin radioOutputCopyToClipboard.Checked := True; - grpFormat.ItemIndex := Owner.Tag; + comboFormat.ItemIndex := Owner.Tag; grpSelection.ItemIndex := 0; // Always use selected cells in copy mode chkIncludeColumnNames.Checked := AppSettings.ReadBool(asGridExportClpColumnNames); chkIncludeAutoIncrement.Checked := AppSettings.ReadBool(asGridExportClpIncludeAutoInc); + chkFocusedColumnOnly.Checked := False; chkIncludeQuery.Checked := False; // Always off in copy mode chkRemoveLinebreaks.Checked := AppSettings.ReadBool(asGridExportClpRemoveLinebreaks); + chkOpenFile.Checked := False; // Always off in copy mode FCSVSeparator := AppSettings.ReadString(asGridExportClpSeparator); FCSVEncloser := AppSettings.ReadString(asGridExportClpEncloser); FCSVTerminator := AppSettings.ReadString(asGridExportClpTerminator); @@ -200,12 +213,14 @@ procedure TfrmExportGrid.FormCreate(Sender: TObject); end else begin radioOutputCopyToClipboard.Checked := AppSettings.ReadBool(asGridExportOutputCopy); radioOutputFile.Checked := AppSettings.ReadBool(asGridExportOutputFile); - grpFormat.ItemIndex := AppSettings.ReadInt(asGridExportFormat); + comboFormat.ItemIndex := AppSettings.ReadInt(asGridExportFormat); grpSelection.ItemIndex := AppSettings.ReadInt(asGridExportSelection); chkIncludeColumnNames.Checked := AppSettings.ReadBool(asGridExportColumnNames); chkIncludeAutoIncrement.Checked := AppSettings.ReadBool(asGridExportIncludeAutoInc); + chkFocusedColumnOnly.Checked := AppSettings.ReadBool(asGridExportFocusedColumnOnly); chkIncludeQuery.Checked := AppSettings.ReadBool(asGridExportIncludeQuery); chkRemoveLinebreaks.Checked := AppSettings.ReadBool(asGridExportRemoveLinebreaks); + chkOpenFile.Checked := AppSettings.ReadBool(asGridExportOpenFile); FCSVSeparator := AppSettings.ReadString(asGridExportSeparator); FCSVEncloser := AppSettings.ReadString(asGridExportEncloser); FCSVTerminator := AppSettings.ReadString(asGridExportTerminator); @@ -215,22 +230,20 @@ procedure TfrmExportGrid.FormCreate(Sender: TObject); end; -procedure TfrmExportGrid.FormResize(Sender: TObject); -begin - grpFormat.Width := Width div 3; - grpSelection.Left := grpFormat.Left + grpFormat.Width + 8; - grpSelection.Width := Width - grpSelection.Left - 24; - grpOptions.Left := grpSelection.Left; - grpOptions.Width := grpSelection.Width; -end; - procedure TfrmExportGrid.FormShow(Sender: TObject); +var + FocusedCol: String; begin // Show dialog. Expect "Grid" property to be set now by the caller. Width := AppSettings.ReadIntDpiAware(asGridExportWindowWidth, Self); Height := AppSettings.ReadIntDpiAware(asGridExportWindowHeight, Self); chkIncludeAutoIncrement.OnClick := CalcSize; + chkFocusedColumnOnly.OnClick := CalcSize; CalcSize(Sender); + // Show name of focused column + FocusedCol := IfThen(Grid.FocusedColumn > NoColumn, Grid.Header.Columns[Grid.FocusedColumn].Text, ''); + chkFocusedColumnOnly.Caption := f_('Only focused column (%s)', [FocusedCol]); + chkFocusedColumnOnly.Enabled := not FocusedCol.IsEmpty; end; @@ -245,12 +258,14 @@ procedure TfrmExportGrid.FormClose(Sender: TObject; var Action: TCloseAction); AppSettings.WriteString(asGridExportFilename, editFilename.Text); AppSettings.WriteString(asGridExportRecentFiles, Implode(DELIM, FRecentFiles)); AppSettings.WriteInt(asGridExportEncoding, comboEncoding.ItemIndex); - AppSettings.WriteInt(asGridExportFormat, grpFormat.ItemIndex); + AppSettings.WriteInt(asGridExportFormat, comboFormat.ItemIndex); AppSettings.WriteInt(asGridExportSelection, grpSelection.ItemIndex); AppSettings.WriteBool(asGridExportColumnNames, chkIncludeColumnNames.Checked); AppSettings.WriteBool(asGridExportIncludeAutoInc, chkIncludeAutoIncrement.Checked); + AppSettings.WriteBool(asGridExportFocusedColumnOnly, chkFocusedColumnOnly.Checked); AppSettings.WriteBool(asGridExportIncludeQuery, chkIncludeQuery.Checked); AppSettings.WriteBool(asGridExportRemoveLinebreaks, chkRemoveLinebreaks.Checked); + AppSettings.WriteBool(asGridExportOpenFile, chkOpenFile.Checked); AppSettings.WriteString(asGridExportSeparator, FCSVSeparator); AppSettings.WriteString(asGridExportEncloser, FCSVEncloser); AppSettings.WriteString(asGridExportTerminator, FCSVTerminator); @@ -295,6 +310,7 @@ procedure TfrmExportGrid.ValidateControls(Sender: TObject); end; chkIncludeQuery.Enabled := ExportFormat in [efHTML, efXML, efMarkDown, efJSON]; + chkOpenFile.Enabled := radioOutputFile.Checked; Enable := ExportFormat = efCSV; lblSeparator.Enabled := Enable; editSeparator.Enabled := Enable; @@ -320,17 +336,19 @@ procedure TfrmExportGrid.ValidateControls(Sender: TObject); function TfrmExportGrid.GetExportFormat: TGridExportFormat; begin - Result := TGridExportFormat(grpFormat.ItemIndex); + // This is slow, don't use in large loops + Result := TGridExportFormat(comboFormat.ItemIndex); end; procedure TfrmExportGrid.SetExportFormat(Value: TGridExportFormat); begin - grpFormat.ItemIndex := Integer(Value); + comboFormat.ItemIndex := Integer(Value); + ValidateControls(Self); end; -procedure TfrmExportGrid.grpFormatClick(Sender: TObject); +procedure TfrmExportGrid.comboFormatSelect(Sender: TObject); var Filename: String; begin @@ -387,7 +405,7 @@ procedure TfrmExportGrid.editFilenameRightButtonClick(Sender: TObject); Dialog.Filter := Dialog.Filter + FormatToDescription[ef] + ' (*.'+FormatToFileExtension[ef]+')|*.'+FormatToFileExtension[ef]+'|'; Dialog.Filter := Dialog.Filter + _('All files')+' (*.*)|*.*'; Dialog.OnTypeChange := SaveDialogTypeChange; - Dialog.FilterIndex := grpFormat.ItemIndex+1; + Dialog.FilterIndex := comboFormat.ItemIndex+1; Dialog.OnTypeChange(Dialog); if Dialog.Execute then begin editFilename.Text := Dialog.FileName; @@ -469,45 +487,63 @@ procedure TfrmExportGrid.CalcSize(Sender: TObject); var GridData: TDBQuery; Node: PVirtualNode; - Col, ExcludeCol: TColumnIndex; + Col, ExcludeAutoIncCol, IncludeFocusedCol: TColumnIndex; ResultCol: Integer; RowNum: PInt64; - SelectionSize, AllSize, RowsCalculated: Int64; + SelectedSize, AllSize: Int64; + CalculatedCount, SelectedCount, AllCount: Int64; + DoIncludeCol: Boolean; begin GridData := Mainform.GridResult(Grid); + if not Assigned(GridData) then begin + MainForm.LogSQL('Failed to get current results'); + Exit; + end; AllSize := 0; - SelectionSize := 0; - chkIncludeAutoIncrement.Enabled := GridData.AutoIncrementColumn > -1; - ExcludeCol := -1; + SelectedSize := 0; + chkIncludeAutoIncrement.Enabled := (GridData.AutoIncrementColumn > -1) and (not chkFocusedColumnOnly.Checked); + ExcludeAutoIncCol := -1; if chkIncludeAutoIncrement.Enabled and (not chkIncludeAutoIncrement.Checked) then - ExcludeCol := GridData.AutoIncrementColumn; + ExcludeAutoIncCol := GridData.AutoIncrementColumn; + IncludeFocusedCol := -1; + if chkFocusedColumnOnly.Enabled and chkFocusedColumnOnly.Checked then + IncludeFocusedCol := Grid.FocusedColumn; Node := GetNextNode(Grid, nil, False); - RowsCalculated := 0; + CalculatedCount := 0; + AllCount := 0; + SelectedCount := 0; while Assigned(Node) do begin - RowNum := Grid.GetNodeData(Node); - GridData.RecNo := RowNum^; - Col := Grid.Header.Columns.GetFirstVisibleColumn(True); - while Col > NoColumn do begin - ResultCol := Col - 1; - if Col <> ExcludeCol then begin - Inc(AllSize, GridData.ColumnLengths(ResultCol)); - if vsSelected in Node.States then - Inc(SelectionSize, GridData.ColumnLengths(ResultCol)); + Inc(AllCount); + if vsSelected in Node.States then + Inc(SelectedCount); + + if CalculatedCount < 1000 then begin + // Performance: use first rows only, and interpolate the rest, see issue #804 + RowNum := Grid.GetNodeData(Node); + GridData.RecNo := RowNum^; + Col := Grid.Header.Columns.GetFirstVisibleColumn(True); + while Col > NoColumn do begin + ResultCol := Col - 1; + DoIncludeCol := (Col <> ExcludeAutoIncCol) and + ((IncludeFocusedCol < 0) or (Col = IncludeFocusedCol)); + if DoIncludeCol then begin + Inc(AllSize, GridData.ColumnLengths(ResultCol)); + if vsSelected in Node.States then + Inc(SelectedSize, GridData.ColumnLengths(ResultCol)); + end; + Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; - Col := Grid.Header.Columns.GetNextVisibleColumn(Col); + Inc(CalculatedCount); end; - // Performance: use first rows only, and interpolate the rest, see issue #804 - Inc(RowsCalculated); - if RowsCalculated >= 1000 then - Break; + Node := GetNextNode(Grid, Node, False); end; - if GridData.RecordCount > RowsCalculated then begin - AllSize := Round(AllSize / RowsCalculated * GridData.RecordCount); + if AllCount > CalculatedCount then begin + AllSize := Round(AllSize / CalculatedCount * AllCount); end; - grpSelection.Items[0] := f_('Selection (%s rows, %s)', [FormatNumber(Grid.SelectedCount), FormatByteNumber(SelectionSize)]); - grpSelection.Items[1] := f_('Complete (%s rows, %s)', [FormatNumber(Grid.RootNodeCount), FormatByteNumber(AllSize)]); + grpSelection.Items[0] := f_('Selection (%s rows, %s)', [FormatNumber(SelectedCount), FormatByteNumber(SelectedSize)]); + grpSelection.Items[1] := f_('Complete (%s rows, %s)', [FormatNumber(AllCount), FormatByteNumber(AllSize)]); end; @@ -567,19 +603,20 @@ procedure TfrmExportGrid.menuCSVClick(Sender: TObject); end; -function TfrmExportGrid.FormatExcelCsv(Text, Encloser: String; DataType: TDBDatatype): String; +function TfrmExportGrid.FormatCsv(Text, Encloser: String; DataType: TDBDatatype; SubFormat: TGridExportFormat): String; begin Result := Text; // Escape encloser characters inside data per de-facto CSV. if not Encloser.IsEmpty then Result := StringReplace(Result, Encloser, Encloser+Encloser, [rfReplaceAll]); - if DataType.Category = dtcTemporal then begin - Result := ReplaceRegExpr('\.(\d+)$', Result, FormatSettings.DecimalSeparator + '$1', True); + // Remove milliseconds from date/time values, unsupported by Excel. See issue #922 + if (SubFormat = efExcel) and (DataType.Category = dtcTemporal) then begin + Result := ReplaceRegExpr('\.(\d+)$', Result, ''); end; end; -function TfrmExportGrid.FormatPhp(Text: String): String; +function TfrmExportGrid.FormatJson(Text: String): String; begin // String escaping for PHP output. Incompatible to TDBConnection.EscapeString. Result := StringReplace(Text, '\', '\\', [rfReplaceAll]); @@ -590,6 +627,28 @@ function TfrmExportGrid.FormatPhp(Text: String): String; Result := '"' + Result + '"'; end; +function TfrmExportGrid.FormatPhp(Text: String): String; +begin + if Text.IndexOfAny([#10, #13, #9, #11, #27, #12]) > -1 then begin + // https://www.php.net/manual/it/language.types.string.php#language.types.string.syntax.double + Result := StringReplace(Text, '\', '\\', [rfReplaceAll]); + Result := StringReplace(Result, #10, '\n', [rfReplaceAll]); + Result := StringReplace(Result, #13, '\r', [rfReplaceAll]); + Result := StringReplace(Result, #9, '\t', [rfReplaceAll]); + Result := StringReplace(Result, #11, '\v', [rfReplaceAll]); + Result := StringReplace(Result, #27, '\e', [rfReplaceAll]); + Result := StringReplace(Result, #12, '\f', [rfReplaceAll]); + Result := StringReplace(Result, '$', '\$', [rfReplaceAll]); + Result := StringReplace(Result, '"', '\"', [rfReplaceAll]); + Result := '"' + Result + '"'; + end else begin + // https://www.php.net/manual/it/language.types.string.php#language.types.string.syntax.single + Result := StringReplace(Text, '\', '\\', [rfReplaceAll]); + Result := StringReplace(Text, '''', '\''', [rfReplaceAll]); + Result := '''' + Result + ''''; + end; +end; + function TfrmExportGrid.FormatLatex(Text: String): String; var @@ -609,7 +668,7 @@ function TfrmExportGrid.FormatLatex(Text: String): String; procedure TfrmExportGrid.btnOKClick(Sender: TObject); var - Col, ExcludeCol: TColumnIndex; + Col, ExcludeAutoIncCol, IncludeFocusedCol: TColumnIndex; ResultCol: Integer; Header, Data, tmp, Encloser, Separator, Terminator, TableName, Filename: String; Node: PVirtualNode; @@ -623,6 +682,13 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); Exporter: TSynExporterHTML; Encoding: TEncoding; Bom: TBytes; + CurrentExportFormat: TGridExportFormat; + + function DoIncludeCol: Boolean; + begin + Result := (Col <> ExcludeAutoIncCol) and + ((IncludeFocusedCol < 0) or (Col = IncludeFocusedCol)) + end; begin Filename := GetOutputFilename(editFilename.Text, MainForm.ActiveDbObj); @@ -651,9 +717,14 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); except TableName := _('UnknownTable'); end; - ExcludeCol := NoColumn; - if (not chkIncludeAutoIncrement.Checked) or (not chkIncludeAutoIncrement.Enabled) then - ExcludeCol := GridData.AutoIncrementColumn + 1; + ExcludeAutoIncCol := NoColumn; + if chkIncludeAutoIncrement.Enabled and (not chkIncludeAutoIncrement.Checked) then + ExcludeAutoIncCol := GridData.AutoIncrementColumn + 1; + IncludeFocusedCol := NoColumn; + if chkFocusedColumnOnly.Checked then + IncludeFocusedCol := Grid.FocusedColumn; + // Calling (Get)ExportFormat is slow, so we store it in a local variable + CurrentExportFormat := ExportFormat; if radioOutputCopyToClipboard.Checked then Encoding := TEncoding.UTF8 @@ -672,57 +743,57 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); // Note that TStringStream + TEncoding.UTF8 do not write a BOM (which is nice), // although it should do so according to TUTF8Encoding.GetPreamble. // Now, only newer Excel versions need that BOM, so we add it explicitly here - S := TStringStream.Create(Header, Encoding); - if (ExportFormat = efExcel) and (Encoding = TEncoding.UTF8) and radioOutputFile.Checked then begin + // P.S.: Note the boolean/False parameter for OwnsEncoding, so our global encodings are not destroyed after usage + S := TStringStream.Create(Header, Encoding, False); + if (CurrentExportFormat = efExcel) and (Encoding = TEncoding.UTF8) and radioOutputFile.Checked then begin Bom := TBytes.Create($EF, $BB, $BF); S.Write(Bom, 3); end; Header := ''; - case ExportFormat of + case CurrentExportFormat of efHTML: begin Header := - '' + CRLF + CRLF + - '' + CRLF + - ' ' + CRLF + - ' ' + TableName + '' + CRLF + - ' ' + CRLF + - ' ' + CRLF + - ' ' + CRLF + - ' ' + CRLF + CRLF + - ' ' + CRLF + CRLF; + CodeIndent(2) + '' + sLineBreak + + CodeIndent + '' + sLineBreak + sLineBreak + + CodeIndent + '' + sLineBreak + sLineBreak; if chkIncludeQuery.Checked then - Header := Header + '

' + GridData.SQL + '

' + CRLF + CRLF; - Header := Header + ' ' + CRLF; + Header := Header + '

' + GridData.SQL + '

' + CRLF + CRLF; + Header := Header + CodeIndent(2) + '
' + sLineBreak; if chkIncludeColumnNames.Checked then begin Header := Header + - ' ' + CRLF + - ' ' + CRLF; + CodeIndent(3) + '' + sLineBreak + + CodeIndent(4) + '' + sLineBreak; Col := Grid.Header.Columns.GetFirstVisibleColumn(True); while Col > NoColumn do begin - if Col <> ExcludeCol then - Header := Header + ' ' + CRLF; + if DoIncludeCol then + Header := Header + CodeIndent(5) + '' + sLineBreak; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; Header := Header + - ' ' + CRLF + - ' ' + CRLF; + CodeIndent(4) + '' + sLineBreak + + CodeIndent(3) + '' + sLineBreak; end; - Header := Header + - ' ' + CRLF; + Header := Header + CodeIndent(3) + '' + sLineBreak; end; efExcel, efCSV: begin @@ -734,7 +805,7 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); while Col > NoColumn do begin // Alter column name in header if data is not raw. ResultCol := Col - 1; - if Col <> ExcludeCol then begin + if DoIncludeCol then begin Data := Grid.Header.Columns[Col].Text; if (GridData.DataType(ResultCol).Category in [dtcBinary, dtcSpatial]) and (not Mainform.actBlobAsText.Checked) then Data := 'HEX(' + Data + ')'; @@ -766,7 +837,7 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); Header := Header + '{'; Col := Grid.Header.Columns.GetFirstVisibleColumn(True); while Col > NoColumn do begin - if Col <> ExcludeCol then + if DoIncludeCol then Header := Header + ' c '; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; @@ -774,7 +845,7 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); if chkIncludeColumnNames.Checked then begin Col := Grid.Header.Columns.GetFirstVisibleColumn(True); while Col > NoColumn do begin - if Col <> ExcludeCol then + if DoIncludeCol then Header := Header + FormatLatex(Grid.Header.Columns[Col].Text) + Separator; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; @@ -784,14 +855,14 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); end; efTextile, efJiraTextile: begin - Separator := IfThen(ExportFormat=efTextile, ' |_. ', ' || '); + Separator := IfThen(CurrentExportFormat=efTextile, ' |_. ', ' || '); Encloser := ''; - Terminator := IfThen(ExportFormat=efTextile, ' |', ' ||') + CRLF; + Terminator := IfThen(CurrentExportFormat=efTextile, ' |', ' ||') + CRLF; if chkIncludeColumnNames.Checked then begin Header := TrimLeft(Separator); Col := Grid.Header.Columns.GetFirstVisibleColumn(True); while Col > NoColumn do begin - if Col <> ExcludeCol then + if DoIncludeCol then Header := Header + Grid.Header.Columns[Col].Text + Separator; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; @@ -804,9 +875,9 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); efPHPArray: begin if radioOutputFile.Checked then - Header := ' NoColumn do begin - if Col <> ExcludeCol then begin + if DoIncludeCol then begin if chkIncludeColumnNames.Checked then Header := Header + Grid.Header.Columns[Col].Text + Separator else @@ -833,7 +904,7 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); Col := Grid.Header.Columns.GetFirstVisibleColumn(True); while Col > NoColumn do begin ResultCol := Col - 1; - if Col <> ExcludeCol then begin + if DoIncludeCol then begin Header := Header + '---'; if GridData.DataType(ResultCol).Category in [dtcInteger, dtcReal] then Header := Header + ':'; @@ -846,12 +917,12 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); efJSON: begin // JavaScript Object Notation - Header := '{' + CRLF; + Header := '{' + sLineBreak; if chkIncludeQuery.Checked then - Header := Header + #9 + '"query": '+FormatPhp(GridData.SQL)+',' + CRLF + Header := Header + #9 + '"query": '+FormatJson(GridData.SQL)+',' + sLineBreak else - Header := Header + #9 + '"table": '+FormatPhp(TableName)+',' + CRLF ; - Header := Header + #9 + '"rows":' + CRLF + #9 + '['; + Header := Header + #9 + '"table": '+FormatJson(TableName)+',' + sLineBreak; + Header := Header + #9 + '"rows":' + sLineBreak + #9 + '['; end; end; @@ -873,10 +944,10 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); GridData.RecNo := RowNum^; // Row preamble - case ExportFormat of - efHTML: tmp := ' ' + CRLF; + case CurrentExportFormat of + efHTML: tmp := CodeIndent(4) + '' + sLineBreak; - efXML: tmp := #9'' + CRLF; + efXML: tmp := CodeIndent + '' + sLineBreak; efSQLUpdate: begin tmp := ''; @@ -885,14 +956,14 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); efSQLInsert, efSQLInsertIgnore, efSQLReplace, efSQLDeleteInsert: begin tmp := ''; - if ExportFormat = efSQLDeleteInsert then begin + if CurrentExportFormat = efSQLDeleteInsert then begin tmp := tmp + 'DELETE FROM ' + GridData.Connection.QuoteIdent(Tablename) + ' WHERE' + GridData.GetWhereClause + ';' + CRLF; end; - if ExportFormat in [efSQLInsert, efSQLDeleteInsert] then + if CurrentExportFormat in [efSQLInsert, efSQLDeleteInsert] then tmp := tmp + 'INSERT' - else if ExportFormat = efSQLInsertIgnore then - tmp := tmp + 'INSERT IGNORE' + else if CurrentExportFormat = efSQLInsertIgnore then + tmp := tmp + 'INSERT IGNORE' else tmp := tmp + 'REPLACE'; tmp := tmp + ' INTO '+GridData.Connection.QuoteIdent(Tablename); @@ -901,7 +972,7 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); Col := Grid.Header.Columns.GetFirstVisibleColumn(True); while Col > NoColumn do begin ResultCol := Col - 1; - if (Col <> ExcludeCol) and (not GridData.ColIsVirtual(ResultCol)) then + if DoIncludeCol and (not GridData.ColIsVirtual(ResultCol)) then tmp := tmp + GridData.Connection.QuoteIdent(Grid.Header.Columns[Col].Text)+', '; Col := Grid.Header.Columns.GetNextVisibleColumn(Col); end; @@ -913,15 +984,22 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); efTextile, efJiraTextile: tmp := TrimLeft(Separator); - efPHPArray: tmp := #9 + 'array('+CRLF; + efPHPArray: tmp := CodeIndent + '[' + sLineBreak; efMarkDown: tmp := '| '; efJSON: begin if chkIncludeColumnNames.Checked then - tmp := CRLF + #9#9 + '{' + CRLF + tmp := sLineBreak + CodeIndent(2) + '{' + sLineBreak + else + tmp := sLineBreak + CodeIndent(2) + '[' + sLineBreak + end; + + efJSONLines: begin + if chkIncludeColumnNames.Checked then + tmp := '{' else - tmp := CRLF + #9#9 + '[' + CRLF + tmp := '['; end else tmp := ''; @@ -931,7 +1009,7 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); Col := Grid.Header.Columns.GetFirstVisibleColumn(True); while Col > NoColumn do begin ResultCol := Col - 1; - if Col <> ExcludeCol then begin + if DoIncludeCol then begin if (GridData.DataType(ResultCol).Category in [dtcBinary, dtcSpatial]) and (not Mainform.actBlobAsText.Checked) then begin Data := GridData.HexValue(ResultCol); @@ -942,29 +1020,28 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); // Keep formatted numeric values if (GridData.DataType(ResultCol).Category in [dtcInteger, dtcReal]) - and (ExportFormat in [efExcel, efHTML, efMarkDown]) then begin + and (CurrentExportFormat in [efExcel, efHTML, efMarkDown]) + then begin Data := FormatNumber(Data, False); end; // Remove linebreaks, see #474 if chkRemoveLinebreaks.Checked then begin - Data := StringReplace(Data, #13#10, ' ', [rfReplaceAll]); - Data := StringReplace(Data, #13, ' ', [rfReplaceAll]); - Data := StringReplace(Data, #10, ' ', [rfReplaceAll]); + StripNewLines(Data); end; - case ExportFormat of + case CurrentExportFormat of efHTML: begin // Escape HTML control characters in data. Data := HTMLSpecialChars(Data); - tmp := tmp + ' ' + CRLF; + tmp := tmp + CodeIndent(5) + '' + sLineBreak; end; efExcel, efCSV: begin if GridData.IsNull(ResultCol) then Data := editNull.Text else begin - Data := FormatExcelCsv(Data, Encloser, GridData.DataType(ResultCol)); + Data := FormatCsv(Data, Encloser, GridData.DataType(ResultCol), CurrentExportFormat); Data := Encloser + Data + Encloser; end; tmp := tmp + Data + Separator; @@ -990,7 +1067,7 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); efXML: begin // Print cell start tag. - tmp := tmp + #9#9' integer conversion in PHP @@ -1036,15 +1113,15 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); end; if chkIncludeColumnNames.Checked then - tmp := tmp + #9#9 + FormatPhp(Grid.Header.Columns[Col].Text) + ' => ' + Data + ','+CRLF + tmp := tmp + CodeIndent(2) + FormatPhp(Grid.Header.Columns[Col].Text) + ' => ' + Data + ',' + sLineBreak else - tmp := tmp + #9#9 + Data + ','+CRLF; + tmp := tmp + CodeIndent(2) + Data + ',' + sLineBreak; end; efJSON: begin - tmp := tmp + #9#9#9; + tmp := tmp + CodeIndent(3); if chkIncludeColumnNames.Checked then - tmp := tmp + FormatPhp(Grid.Header.Columns[Col].Text) + ': '; + tmp := tmp + FormatJson(Grid.Header.Columns[Col].Text) + ': '; if GridData.IsNull(ResultCol) then tmp := tmp + 'null,' +CRLF else begin @@ -1052,12 +1129,28 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); dtcInteger, dtcReal: tmp := tmp + Data; else - tmp := tmp + FormatPhp(Data) + tmp := tmp + FormatJson(Data) end; tmp := tmp + ',' + CRLF; end; end; + efJSONLines: begin + if chkIncludeColumnNames.Checked then + tmp := tmp + FormatJson(Grid.Header.Columns[Col].Text) + ': '; + if GridData.IsNull(ResultCol) then + tmp := tmp + 'null, ' + else begin + case GridData.DataType(ResultCol).Category of + dtcInteger, dtcReal: + tmp := tmp + Data; + else + tmp := tmp + FormatJson(Data) + end; + tmp := tmp + ', '; + end; + end; + end; end; @@ -1065,15 +1158,15 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); end; // Row epilogue - case ExportFormat of + case CurrentExportFormat of efHTML: - tmp := tmp + ' ' + CRLF; + tmp := tmp + CodeIndent(4) + '' + sLineBreak; efExcel, efCSV, efLaTeX, efTextile, efJiraTextile: begin Delete(tmp, Length(tmp)-Length(Separator)+1, Length(Separator)); tmp := tmp + Terminator; end; efXML: - tmp := tmp + #9'' + CRLF; + tmp := tmp + CodeIndent + '' + sLineBreak; efSQLInsert, efSQLInsertIgnore, efSQLReplace, efSQLDeleteInsert: begin Delete(tmp, Length(tmp)-1, 2); tmp := tmp + ');' + CRLF; @@ -1083,15 +1176,22 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); tmp := tmp + ' WHERE' + GridData.GetWhereClause + ';' + sLineBreak; end; efPHPArray: - tmp := tmp + #9 + '),' + CRLF; + tmp := tmp + CodeIndent + '],' + sLineBreak; efMarkDown: tmp := tmp + Terminator; efJSON: begin Delete(tmp, length(tmp)-2,2); if chkIncludeColumnNames.Checked then - tmp := tmp + #9#9 + '},' + tmp := tmp + CodeIndent(2) + '},' + else + tmp := tmp + CodeIndent(2) + '],'; + end; + efJSONLines: begin + Delete(tmp, length(tmp)-1,2); + if chkIncludeColumnNames.Checked then + tmp := tmp + '}' + #10 else - tmp := tmp + #9#9 + '],'; + tmp := tmp + ']' + #10; end; end; S.WriteString(tmp); @@ -1100,17 +1200,17 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); end; // Footer - case ExportFormat of + case CurrentExportFormat of efHTML: begin tmp := - ' ' + CRLF + - '
' + Grid.Header.Columns[Col].Text + '' + Grid.Header.Columns[Col].Text + '
' + Data + '' + Data + '
' + CRLF + CRLF + - '

' + CRLF + - ' generated ' + DateToStr(now) + ' ' + TimeToStr(now) + - ' by ' + APPNAME + ' ' + Mainform.AppVersion + '' + CRLF + - '

' + CRLF + CRLF + - ' ' + CRLF + - '' + CRLF; + CodeIndent(3) + '' + sLineBreak + + CodeIndent(2) + '' + sLineBreak + sLineBreak + + CodeIndent(2) + '

' + sLineBreak + + CodeIndent(3) + 'generated ' + DateToStr(now) + ' ' + TimeToStr(now) + + CodeIndent(3) + 'by ' + APPNAME + ' ' + Mainform.AppVersion + '' + sLineBreak + + CodeIndent(2) + '

' + sLineBreak + sLineBreak + + CodeIndent + '' + sLineBreak + + '' + sLineBreak; end; efXML: begin if chkIncludeQuery.Checked then @@ -1121,11 +1221,11 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); efLaTeX: tmp := '\end{tabular}' + CRLF; efPHPArray: begin - tmp := ');' + CRLF; + tmp := '];' + CRLF; end; efJSON: begin S.Size := S.Size - 1; - tmp := CRLF + #9 + ']' + CRLF + '}'; + tmp := sLineBreak + CodeIndent + ']' + sLineBreak + '}'; end; else tmp := ''; @@ -1136,7 +1236,7 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); HTML := nil; // SynEdit's exporter is slow on large strings, see issue #2903 if S.Size < 100*SIZE_KB then begin - case ExportFormat of + case CurrentExportFormat of efSQLInsert, efSQLInsertIgnore, efSQLReplace, efSQLDeleteInsert: begin Exporter := TSynExporterHTML.Create(Self); Exporter.Highlighter := MainForm.SynSQLSynUsed; @@ -1152,6 +1252,8 @@ procedure TfrmExportGrid.btnOKClick(Sender: TObject); end else begin try S.SaveToFile(Filename); + if chkOpenFile.Checked then + ShellExec(editFilename.Text); except on E:EFCreateError do begin // Keep form open if file cannot be created diff --git a/source/extra_controls.pas b/source/extra_controls.pas index 73b9f5871..3ec77abc7 100644 --- a/source/extra_controls.pas +++ b/source/extra_controls.pas @@ -6,13 +6,14 @@ interface System.Classes, System.SysUtils, Vcl.Forms, Winapi.Windows, Winapi.Messages, System.Types, Vcl.StdCtrls, Vcl.Clipbrd, SizeGrip, apphelpers, Vcl.Graphics, Vcl.Dialogs, gnugettext, Vcl.ImgList, Vcl.VirtualImageList, Vcl.ComCtrls, Winapi.ShLwApi, Vcl.ExtCtrls, VirtualTrees, VirtualTrees.Types, SynRegExpr, Vcl.Controls, Winapi.ShlObj, - SynEditMiscClasses, SynUnicode; + SynEditMiscClasses, SynUnicode, Vcl.Themes, Vcl.GraphUtil, Math; type // Form with a sizegrip in the lower right corner, without the need for a statusbar TExtForm = class(TForm) private FSizeGrip: TSizeGripXP; + FPixelsPerInchDesigned: Integer; function GetHasSizeGrip: Boolean; procedure SetHasSizeGrip(Value: Boolean); protected @@ -30,6 +31,7 @@ TExtForm = class(TForm) function ScaleSize(x: Extended): Integer; overload; class function ScaleSize(x: Extended; Control: TControl): Integer; overload; class procedure PageControlTabHighlight(PageControl: TPageControl); + property PixelsPerInchDesigned: Integer read FPixelsPerInchDesigned; end; // Modern file-open-dialog with high DPI support and encoding selector @@ -83,6 +85,26 @@ TExtSynHotKey = class(TSynHotKey) property OnExit: TNotifyEvent read FOnExit write FOnExit; end; + TExtComboBox = class(TComboBox) + private + FcbHintIndex: Integer; + FHintWindow: THintWindow; + protected + procedure Change; override; + procedure DropDown; override; + procedure CloseUp; override; + procedure InitiateAction; override; + end; + + TExtHintWindow = class(THintWindow) + private + const Padding: Integer = 8; + protected + procedure Paint; override; + public + function CalcHintRect(MaxWidth: Integer; const AHint: string; AData: TCustomData): TRect; override; + end; + implementation @@ -95,6 +117,7 @@ constructor TExtForm.Create(AOwner: TComponent); begin inherited; + FPixelsPerInchDesigned := 96; InheritFont(Font); HasSizeGrip := False; @@ -359,7 +382,7 @@ procedure TExtForm.FilterNodesByEdit(Edit: TButtonedEdit; Tree: TVirtualStringTr var rx: TRegExpr; Node: PVirtualNode; - i: Integer; + i, ColumnCount: Integer; match: Boolean; CellText: String; begin @@ -384,8 +407,10 @@ procedure TExtForm.FilterNodesByEdit(Edit: TButtonedEdit; Tree: TVirtualStringTr if not Tree.HasChildren[Node] then begin // Don't filter anything if the filter text is empty match := rx.Expression = ''; + // Suport trees with 0 defined columns, like the shortcut tree in preferences + ColumnCount := Max(Tree.Header.Columns.Count, 1); // Search for given text in node's captions - if not match then for i := 0 to Tree.Header.Columns.Count - 1 do begin + if not match then for i:=0 to ColumnCount - 1 do begin CellText := Tree.Text[Node, i]; match := rx.Exec(CellText); if match then @@ -422,22 +447,32 @@ class procedure TExtForm.PageControlTabHighlight(PageControl: TPageControl); var i, CurrentImage, CountOriginals: Integer; Images: TVirtualImageList; + GrayscaleMode: Integer; + IsQueryTab, DoGrayscale: Boolean; begin // Set grayscale icon on inactive tabs if not (PageControl.Images is TVirtualImageList) then Exit; + GrayscaleMode := AppSettings.ReadInt(asTabIconsGrayscaleMode); + Images := PageControl.Images as TVirtualImageList; CountOriginals := Images.ImageCollection.Count; for i:=0 to PageControl.PageCount-1 do begin CurrentImage := PageControl.Pages[i].ImageIndex; if PageControl.ActivePageIndex = i then begin - if CurrentImage >= CountOriginals then + if CurrentImage >= CountOriginals then begin + // Grayscaled => Color PageControl.Pages[i].ImageIndex := CurrentImage - CountOriginals; + end; end else begin - if CurrentImage < CountOriginals then - PageControl.Pages[i].ImageIndex := CurrentImage + CountOriginals; + if CurrentImage < CountOriginals then begin + // Color => Grayscaled + IsQueryTab := (PageControl.Owner.Name = 'MainForm') and ExecRegExpr('^tabQuery\d*$', PageControl.Pages[i].Name); + if ((GrayscaleMode = 1) and IsQueryTab) or (GrayscaleMode = 2) then + PageControl.Pages[i].ImageIndex := CurrentImage + CountOriginals; + end; end; end; end; @@ -640,4 +675,128 @@ procedure TExtSynHotKey.Paint; end; + +{ TExtComboBox } + +procedure TExtComboBox.Change; +var + P: TPoint; + HintRect: TRect; + HintText: String; + HintWidth, Padding: Integer; +begin + inherited; + if (ItemIndex > -1) and DroppedDown and GetCursorPos(P) then begin + HintText := Items[ItemIndex]; + HintWidth := Canvas.TextWidth(HintText); + if HintWidth > Width then begin + Padding := TExtForm.ScaleSize(10, Self); + HintRect := Rect( + P.X + Padding, + P.Y + Padding * 2, + P.X + HintWidth + Padding * 3, + P.Y + Padding * 4 + ); + FHintWindow.ActivateHint(HintRect, HintText); + end; + end; +end; + +procedure TExtComboBox.CloseUp; +begin + inherited; + FHintWindow.Hide; + ControlStyle := ControlStyle - [csActionClient]; +end; + +procedure TExtComboBox.DropDown; +begin + inherited; + if not Assigned(FHintWindow) then + FHintWindow := THintWindow.Create(Self); + FcbHintIndex := -1; + ControlStyle := ControlStyle + [csActionClient]; +end; + +procedure TExtComboBox.InitiateAction; +var + Idx: Integer; +begin + inherited; + Idx := ItemIndex; + if Idx <> FcbHintIndex then + begin + FcbHintIndex := ItemIndex; + Change; + end; +end; + + + +{ TExtHintWindow } + + +function TExtHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: string; AData: TCustomData): TRect; +begin + Result := inherited; + // Customized: enlarge surrounding rect to make space for padding + if AHint.Contains(SLineBreak) then begin + Result.Right := Result.Right + 2 * ScaleValue(Padding); + Result.Bottom := Result.Bottom + 2 * ScaleValue(Padding); + end; +end; + + +procedure TExtHintWindow.Paint; +var + R, ClipRect: TRect; + LColor: TColor; + LStyle: TCustomStyleServices; + LDetails: TThemedElementDetails; + LGradientStart, LGradientEnd, LTextColor: TColor; +begin + R := ClientRect; + LStyle := StyleServices(Screen.ActiveForm); + LTextColor := Screen.HintFont.Color; + if LStyle.Enabled then + begin + ClipRect := R; + InflateRect(R, 4, 4); + if TOSVersion.Check(6) and LStyle.IsSystemStyle then + begin + // Paint Windows gradient background + LStyle.DrawElement(Canvas.Handle, LStyle.GetElementDetails(tttStandardNormal), R, ClipRect); + end + else + begin + LDetails := LStyle.GetElementDetails(thHintNormal); + if LStyle.GetElementColor(LDetails, ecGradientColor1, LColor) and (LColor <> clNone) then + LGradientStart := LColor + else + LGradientStart := clInfoBk; + if LStyle.GetElementColor(LDetails, ecGradientColor2, LColor) and (LColor <> clNone) then + LGradientEnd := LColor + else + LGradientEnd := clInfoBk; + if LStyle.GetElementColor(LDetails, ecTextColor, LColor) and (LColor <> clNone) then + LTextColor := LColor + else + LTextColor := Screen.HintFont.Color; + GradientFillCanvas(Canvas, LGradientStart, LGradientEnd, R, gdVertical); + end; + R := ClipRect; + end; + Inc(R.Left, 2); + Inc(R.Top, 2); + // Customized: move inner rect right+down to add padding to outer edge + if String(Caption).Contains(SLineBreak) then begin + Inc(R.Left, ScaleValue(Padding)); + Inc(R.Top, ScaleValue(Padding)); + end; + Canvas.Font.Color := LTextColor; + DrawText(Canvas.Handle, Caption, -1, R, DT_LEFT or DT_NOPREFIX or + DT_WORDBREAK or DrawTextBiDiModeFlagsReadingOnly); +end; + + end. diff --git a/source/grideditlinks.pas b/source/grideditlinks.pas index b7f634914..200e72337 100644 --- a/source/grideditlinks.pas +++ b/source/grideditlinks.pas @@ -5,7 +5,7 @@ interface uses - Winapi.Windows, Vcl.Forms, Vcl.Graphics, Winapi.Messages, VirtualTrees, VirtualTrees.Types, Vcl.ComCtrls, System.SysUtils, System.Classes, + Winapi.Windows, Vcl.Forms, Vcl.Graphics, Winapi.Messages, VirtualTrees, VirtualTrees.BaseTree, VirtualTrees.Types, Vcl.ComCtrls, System.SysUtils, System.Classes, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.CheckLst, Vcl.Controls, System.Types, Vcl.Dialogs, Vcl.Menus, Vcl.Mask, System.DateUtils, System.Math, dbconnection, dbstructures, apphelpers, texteditor, bineditor, gnugettext, System.StrUtils, System.UITypes, SynRegExpr, Vcl.Themes, extra_controls; @@ -107,7 +107,7 @@ TDateTimeEditorLink = class(TBaseGridEditorLink) TEnumEditorLink = class(TBaseGridEditorLink) private - FCombo: TComboBox; + FCombo: TExtComboBox; procedure DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure DoSelect(Sender: TObject); public @@ -146,7 +146,6 @@ TInplaceEditorLink = class(TBaseGridEditorLink) FPanel: TPanel; FEdit: TEdit; FButton: TButton; - FTextEditor: TfrmTextEditor; FMaxLength: Integer; procedure DoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure ButtonClick(Sender: TObject); @@ -156,7 +155,6 @@ TInplaceEditorLink = class(TBaseGridEditorLink) constructor Create(Tree: TVirtualStringTree; AllowEdit: Boolean; Col: TTableColumn); override; destructor Destroy; override; function BeginEdit: Boolean; override; - function CancelEdit: Boolean; override; function EndEdit: Boolean; override; function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; override; procedure SetBounds(R: TRect); override; @@ -612,15 +610,22 @@ function TDateTimeEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualN if not Result then Exit; FMaskEdit.ReadOnly := not FAllowEdit; + case FTableColumn.DataType.Index of dbdtDate: FMaskEdit.EditMask := '0000-00-00;1; '; - dbdtDatetime, dbdtDatetime2, dbdtTimestamp, dbdtInt, dbdtBigint: begin + + dbdtDatetime, dbdtDatetime2, dbdtTimestamp, + dbdtInt, dbdtBigint, + dbdtFloat, dbdtDouble, dbdtDecimal, dbdtNumeric, dbdtReal, dbdtDoublePrecision: begin + if FCellText.IsEmpty then + FCellText := DateTimeToStr(Now); if MicroSecondsPrecision > 0 then FMaskEdit.EditMask := '0000-00-00 00\:00\:00.'+StringOfChar('0', MicroSecondsPrecision)+';1; ' else FMaskEdit.EditMask := '0000-00-00 00\:00\:00;1; '; end; + dbdtTime: begin ForceTextLen := 10; if MicroSecondsPrecision > 0 then begin @@ -631,6 +636,7 @@ function TDateTimeEditorLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualN while Length(FCellText) < ForceTextLen do FCellText := ' ' + FCellText; end; + dbdtYear: FMaskEdit.EditMask := '0000;1; '; end; @@ -732,7 +738,8 @@ procedure TDateTimeEditorLink.ModifyDate(Offset: Integer); i, MaxSeconds, MinSeconds: Int64; text: String; OldSelStart, OldSelLength, - ms, DotPos: Integer; + ms: Integer; + msStr, StrWithoutMs: String; function TimeToSeconds(Str: String): Int64; var @@ -768,10 +775,11 @@ procedure TDateTimeEditorLink.ModifyDate(Offset: Integer); try // Detect microseconds part of value if any if MicroSecondsPrecision > 0 then begin - DotPos := Length(FMaskEdit.Text) - Pos('.', ReverseString(FMaskEdit.Text)) + 2; - ms := MakeInt(Copy(FMaskEdit.Text, DotPos, Length(FMaskEdit.Text))); - end else + msStr := RegExprGetMatch('\.(\d+)$', FMaskEdit.Text, 1); + ms := MakeInt(msStr); + end else begin ms := 0; + end; case FTableColumn.DataType.Index of dbdtYear: begin @@ -791,8 +799,11 @@ procedure TDateTimeEditorLink.ModifyDate(Offset: Integer); text := DateToStr(d); end; - dbdtDateTime, dbdtDateTime2, dbdtTimestamp, dbdtInt, dbdtBigint: begin - dt := StrToDateTime(FMaskEdit.Text); + dbdtDateTime, dbdtDateTime2, dbdtTimestamp, + dbdtInt, dbdtBigint, + dbdtFloat, dbdtDouble, dbdtDecimal, dbdtNumeric, dbdtReal, dbdtDoublePrecision: begin + StrWithoutMs := ReplaceRegExpr('\.\d+$', FMaskEdit.Text, ''); + dt := StrToDateTime(StrWithoutMs); case FMaskEdit.SelStart of 0..3: dt := IncYear(dt, Offset); 5,6: dt := IncMonth(dt, Offset); @@ -840,11 +851,9 @@ procedure TDateTimeEditorLink.ModifyDate(Offset: Integer); FMaskEdit.SelLength := OldSelLength; end; except - on E:EConvertError do begin - // Ignore any DateToStr exception. Should only appear in cases where the users - // enters invalid dates - end else - raise; + on E:Exception do begin + MainForm.LogSQL(E.Message); + end; end; end; @@ -858,23 +867,39 @@ procedure TDateTimeEditorLink.TextChange; function TDateTimeEditorLink.MicroSecondsPrecision: Integer; var rx: TRegExpr; -begin - if not FTableColumn.LengthSet.IsEmpty then - Result := MakeInt(FTableColumn.LengthSet) - else begin - // Find default length of supported microseconds in datatype definition - // See dbstructures - rx := TRegExpr.Create; - rx.Expression := '\.([^\.]+)$'; - if rx.Exec(FTableColumn.DataType.Format) then - Result := rx.MatchLen[1] - else + msStr: String; +begin + case FTableColumn.DataType.Category of + dtcTemporal: begin + if not FTableColumn.LengthSet.IsEmpty then + // Read microseconds precision from MySQL length/set + Result := MakeInt(FTableColumn.LengthSet) + else begin + // Find default length of supported microseconds in datatype definition + // See dbstructures + rx := TRegExpr.Create; + rx.Expression := '\.([^\.]+)$'; + if rx.Exec(FTableColumn.DataType.Format) then + Result := rx.MatchLen[1] + else + Result := 0; + rx.Free; + end; + end; + + dtcInteger: begin + // UNIX timestamps from integers Result := 0; - rx.Free; + end; + + dtcReal: begin + // UNIX timestamps from floats + // Detect number of decimals from original cell string + msStr := RegExprGetMatch('\.(\d+)$', FCellText, 1); + Result := Length(msStr); + end; + end; - // No microseconds for UNIX timestamp columns - if FTableColumn.DataType.Index in [dbdtInt, dbdtBigint] then - Result := 0; end; @@ -886,7 +911,7 @@ constructor TEnumEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boolean; inherited; AllowCustomText := False; ItemMustExist := False; - FCombo := TComboBox.Create(FParentForm); + FCombo := TExtComboBox.Create(FParentForm); FCombo.Hide; FCombo.Parent := FParentForm; FCombo.OnKeyDown := DoKeyDown; @@ -921,8 +946,12 @@ function TEnumEditorLink.EndEdit: Boolean; stdcall; var NewText: String; begin - if AllowCustomText and FAllowEdit and (not ItemMustExist) then - NewText := FCombo.Text + if AllowCustomText and FAllowEdit then begin + if (not ItemMustExist) or ValueList.Contains(FCombo.Text) then + NewText := FCombo.Text + else + NewText := ''; + end else if (ValueList.Count > 0) and (FCombo.ItemIndex > -1) then NewText := ValueList[FCombo.ItemIndex] else @@ -989,7 +1018,7 @@ constructor TSetEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boolean; FPanel.Hide; FPanel.Parent := FParentForm; FPanel.ParentBackground := False; - FPanel.Height := 150; + FPanel.Height := TExtForm.ScaleSize(150, FParentForm); FPanel.OnExit := DoEndEdit; FCheckList := TCheckListBox.Create(FPanel); @@ -1080,7 +1109,7 @@ procedure TSetEditorLink.SetBounds(R: TRect); stdcall; FBtnOk.Width := (FPanel.Width - 3*margin) div 2; FBtnOk.Left := margin; - FBtnOk.Height := 24; + FBtnOk.Height := TExtForm.ScaleSize(24, FParentForm); FBtnOk.Top := FPanel.Height - 2*margin - FBtnOk.Height; FBtnOk.Enabled := FAllowEdit; @@ -1120,7 +1149,6 @@ constructor TInplaceEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boole begin inherited; ButtonVisible := false; - FTextEditor := nil; FPanel := TPanel.Create(FParentForm); FPanel.Parent := FParentForm; @@ -1147,10 +1175,11 @@ constructor TInplaceEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: Boole destructor TInplaceEditorLink.Destroy; begin - if Assigned(FTextEditor) then - FTextEditor.Release; - if not ((csDestroying in FPanel.ComponentState) or (csCreating in FPanel.ControlState)) then + if not ((csDestroying in FPanel.ComponentState) or (csCreating in FPanel.ControlState)) then begin + FEdit.Free; + FButton.Free; FPanel.Free; + end; inherited; end; @@ -1160,7 +1189,7 @@ function TInplaceEditorLink.BeginEdit: Boolean; if Result then begin FButton.Visible := ButtonVisible; SetBounds(Rect(0, 0, 0, 0)); - if (Length(FEdit.Text) > SIZE_KB) or (ScanLineBreaks(FEdit.Text) <> lbsNone) then + if (Length(FEdit.Text) >= GRIDMAXDATA) or (ScanLineBreaks(FEdit.Text) <> lbsNone) then ButtonClick(FTree) else begin FPanel.Show; @@ -1169,14 +1198,6 @@ function TInplaceEditorLink.BeginEdit: Boolean; end; end; -function TInplaceEditorLink.CancelEdit: Boolean; -begin - Result := inherited CancelEdit; - if Result then begin - if Assigned(FTextEditor) then - FTextEditor.Close; - end; -end; function TInplaceEditorLink.EndEdit: Boolean; var @@ -1184,14 +1205,8 @@ function TInplaceEditorLink.EndEdit: Boolean; begin Result := not FStopping; if FStopping then Exit; - if Assigned(FTextEditor) then begin - NewText := FTextEditor.GetText; - FModified := FTextEditor.Modified; - FTextEditor.Close; - end else begin - NewText := FEdit.Text; - FModified := FEdit.Modified; - end; + NewText := FEdit.Text; + FModified := NewText <> FCellText; Result := EndEditHelper(NewText); end; @@ -1204,21 +1219,30 @@ procedure TInplaceEditorLink.DoKeyDown(Sender: TObject; var Key: Word; end; procedure TInplaceEditorLink.ButtonClick(Sender: TObject); +var + Editor: TfrmTextEditor; begin if not FButton.Visible then Exit; // Button was invisible, but hotkey was pressed - FTextEditor := TfrmTextEditor.Create(FTree); - FTextEditor.SetFont(MainForm.SynMemoQuery.Font); - FTextEditor.SetText(FEdit.Text); + Editor := TfrmTextEditor.Create(FTree); + Editor.SetFont(MainForm.SynMemoQuery.Font); + Editor.SetText(FEdit.Text); if FEdit.HandleAllocated then begin - FTextEditor.MemoText.SelStart := FEdit.SelStart; - FTextEditor.MemoText.SelLength := FEdit.SelLength; + Editor.MemoText.SelStart := FEdit.SelStart; + Editor.MemoText.SelLength := FEdit.SelLength; + end; + Editor.SetTitleText(TitleText); + Editor.Modified := FEdit.Modified; + Editor.SetMaxLength(FMaxLength); + Editor.TableColumn := FTableColumn; + Editor.MemoText.ReadOnly := not FAllowEdit; + if Editor.ShowModal = mrYes then begin + FEdit.Text := Editor.GetText; + DoEndEdit(Sender); + end + else begin + DoCancelEdit(Sender); end; - FTextEditor.SetTitleText(TitleText); - FTextEditor.Modified := FEdit.Modified; - FTextEditor.SetMaxLength(FMaxLength); - FTextEditor.TableColumn := FTableColumn; - FTextEditor.MemoText.ReadOnly := not FAllowEdit; - FTextEditor.ShowModal; + Editor.Free; end; function TInplaceEditorLink.PrepareEdit(Tree: TBaseVirtualTree; @@ -1359,7 +1383,7 @@ constructor TColumnDefaultEditorLink.Create(Tree: TVirtualStringTree; AllowEdit: FRadioAutoInc.Width := FRadioAutoInc.Parent.Width - 2 * FRadioAutoInc.Left; FRadioAutoInc.OnClick := RadioClick; FRadioAutoInc.OnKeyDown := DoKeyDown; - FRadioAutoInc.Caption := Col.AutoIncName; + FRadioAutoInc.Caption := FTableColumn.Connection.SqlProvider.GetSql(qAutoInc); FBtnOk := TButton.Create(FPanel); FBtnOk.Parent := FPanel; @@ -1523,7 +1547,7 @@ function TColumnDefaultEditorLink.EndEdit: Boolean; stdcall; cdtText: Col.DefaultText := FTextEdit.Text; cdtNull: Col.DefaultText := 'NULL'; cdtExpression: Col.DefaultText := FExpressionEdit.Text; - cdtAutoInc: Col.DefaultText := Col.AutoIncName; + cdtAutoInc: Col.DefaultText := Col.Connection.SqlProvider.GetSql(qAutoInc); end; if FOnUpdateEdit.Text <> '' then diff --git a/source/insertfiles.pas b/source/insertfiles.pas index 9571be823..38e7bd892 100644 --- a/source/insertfiles.pas +++ b/source/insertfiles.pas @@ -5,7 +5,8 @@ interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Winapi.ShellApi, System.Math, Vcl.Graphics, Vcl.ComCtrls, Vcl.ToolWin, extra_controls, - dbconnection, dbstructures, VirtualTrees, grideditlinks, SynRegExpr, gnugettext, apphelpers; + dbconnection, dbstructures, VirtualTrees, grideditlinks, SynRegExpr, gnugettext, apphelpers, VirtualTrees.BaseTree, VirtualTrees.Types, + VirtualTrees.BaseAncestorVCL, VirtualTrees.AncestorVCL; type TColInfo = class @@ -118,6 +119,8 @@ procedure TfrmInsertFiles.FormCreate(Sender: TObject); HasSizeGrip := True; ListFiles.Images := GetSystemImageList; DragAcceptFiles(Handle, True); + FixVT(ListFiles); + FixVT(ListColumns); end; @@ -136,8 +139,6 @@ procedure TfrmInsertFiles.FormShow(Sender: TObject); Height := AppSettings.ReadIntDpiAware(asFileImportWindowHeight, Self); RestoreListSetup(ListColumns); RestoreListSetup(ListFiles); - FixVT(ListFiles); - FixVT(ListColumns); FConnection := Mainform.ActiveConnection; Caption := FConnection.Parameters.SessionName + ' - ' + MainForm.actInsertFiles.Caption; comboDBs.Items.Clear; @@ -658,6 +659,7 @@ procedure TfrmInsertFiles.btnInsertClick(Sender: TObject); sql := sql + ')'; try FConnection.Query(sql); + FConnection.ShowWarnings; Mainform.ProgressStep; except on E:EDbError do begin diff --git a/source/loaddata.dfm b/source/loaddata.dfm index e92a96d29..e49ed410a 100644 --- a/source/loaddata.dfm +++ b/source/loaddata.dfm @@ -2,10 +2,10 @@ object loaddataform: Tloaddataform Left = 212 Top = 111 Caption = 'Import text file' - ClientHeight = 494 - ClientWidth = 509 + ClientHeight = 548 + ClientWidth = 513 Color = clBtnFace - Constraints.MinHeight = 530 + Constraints.MinHeight = 550 Constraints.MinWidth = 525 Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText @@ -18,12 +18,12 @@ object loaddataform: Tloaddataform OnResize = FormResize OnShow = FormShow DesignSize = ( - 509 - 494) + 513 + 548) TextHeight = 14 object btnImport: TButton Left = 345 - Top = 461 + Top = 515 Width = 75 Height = 25 Anchors = [akRight, akBottom] @@ -36,7 +36,7 @@ object loaddataform: Tloaddataform end object btnCancel: TButton Left = 426 - Top = 461 + Top = 515 Width = 75 Height = 25 Anchors = [akRight, akBottom] @@ -59,23 +59,23 @@ object loaddataform: Tloaddataform object lblFilename: TLabel Left = 10 Top = 27 - Width = 46 - Height = 13 + Width = 51 + Height = 14 Caption = 'Filename:' FocusControl = editFilename end object lblEncoding: TLabel Left = 10 Top = 54 - Width = 47 - Height = 13 + Width = 54 + Height = 14 Caption = 'Encoding:' end object editFilename: TButtonedEdit Left = 88 Top = 24 Width = 395 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.ImageIndex = 51 @@ -90,7 +90,7 @@ object loaddataform: Tloaddataform Left = 88 Top = 51 Width = 395 - Height = 21 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] DropDownCount = 16 @@ -112,36 +112,36 @@ object loaddataform: Tloaddataform object lblFieldTerminater: TLabel Left = 10 Top = 26 - Width = 97 - Height = 13 + Width = 110 + Height = 14 Caption = 'Fields terminated by' end object lblFieldEncloser: TLabel Left = 10 Top = 51 - Width = 87 - Height = 13 + Width = 98 + Height = 14 Caption = 'Fields enclosed by' end object lblFieldEscaper: TLabel Left = 10 Top = 75 - Width = 85 - Height = 13 + Width = 95 + Height = 14 Caption = 'Fields escaped by' end object lblLineTerminator: TLabel Left = 10 Top = 100 - Width = 94 - Height = 13 + Width = 108 + Height = 14 Caption = 'Lines terminated by' end object editFieldEscaper: TEdit Left = 145 Top = 72 Width = 49 - Height = 21 + Height = 22 TabOrder = 3 Text = '"' end @@ -149,7 +149,7 @@ object loaddataform: Tloaddataform Left = 145 Top = 48 Width = 49 - Height = 21 + Height = 22 TabOrder = 1 Text = '"' end @@ -157,7 +157,7 @@ object loaddataform: Tloaddataform Left = 145 Top = 23 Width = 49 - Height = 21 + Height = 22 TabOrder = 0 Text = ';' end @@ -176,7 +176,7 @@ object loaddataform: Tloaddataform Left = 145 Top = 97 Width = 49 - Height = 21 + Height = 22 TabOrder = 4 Text = '\r\n' end @@ -185,32 +185,32 @@ object loaddataform: Tloaddataform Left = 8 Top = 98 Width = 209 - Height = 135 + Height = 175 Anchors = [akLeft, akTop, akRight] Caption = 'Options' TabOrder = 1 DesignSize = ( 209 - 135) + 175) object lblIgnoreLinesCount: TLabel Left = 143 Top = 26 - Width = 21 - Height = 13 + Width = 23 + Height = 14 Caption = 'lines' end object lblIgnoreLines: TLabel Left = 10 Top = 26 - Width = 54 - Height = 13 + Width = 60 + Height = 14 Caption = 'Ignore first' end object updownIgnoreLines: TUpDown Left = 121 Top = 23 Width = 16 - Height = 21 + Height = 22 Associate = editIgnoreLines Max = 32767 Position = 1 @@ -220,7 +220,7 @@ object loaddataform: Tloaddataform Left = 88 Top = 23 Width = 33 - Height = 21 + Height = 22 TabOrder = 0 Text = '1' end @@ -257,10 +257,19 @@ object loaddataform: Tloaddataform Caption = 'Truncate destination table before import' TabOrder = 4 end + object chkKeepDialogOpen: TCheckBox + Left = 10 + Top = 138 + Width = 196 + Height = 17 + Anchors = [akLeft, akTop, akRight] + Caption = 'Keep dialog open after import' + TabOrder = 5 + end end object grpDuplicates: TRadioGroup Left = 8 - Top = 239 + Top = 279 Width = 209 Height = 123 Anchors = [akLeft, akTop, akRight] @@ -274,9 +283,9 @@ object loaddataform: Tloaddataform end object grpParseMethod: TRadioGroup Left = 8 - Top = 368 + Top = 408 Width = 209 - Height = 87 + Height = 101 Anchors = [akLeft, akTop, akRight, akBottom] Caption = 'Method' ItemIndex = 0 @@ -291,39 +300,39 @@ object loaddataform: Tloaddataform Left = 223 Top = 239 Width = 278 - Height = 216 + Height = 270 Anchors = [akTop, akRight, akBottom] Caption = 'Destination' TabOrder = 5 DesignSize = ( 278 - 216) + 270) object lblDatabase: TLabel Left = 10 Top = 24 - Width = 50 - Height = 13 + Width = 54 + Height = 14 Caption = 'Database:' end object lblTable: TLabel Left = 10 Top = 48 - Width = 30 - Height = 13 + Width = 34 + Height = 14 Caption = 'Table:' end object lblColumns: TLabel Left = 10 Top = 72 - Width = 44 - Height = 13 + Width = 49 + Height = 14 Caption = 'Columns:' end object comboDatabase: TComboBox Left = 112 Top = 21 Width = 156 - Height = 21 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] TabOrder = 0 @@ -333,7 +342,7 @@ object loaddataform: Tloaddataform Left = 112 Top = 45 Width = 156 - Height = 21 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] TabOrder = 1 @@ -343,9 +352,9 @@ object loaddataform: Tloaddataform Left = 112 Top = 72 Width = 153 - Height = 129 + Height = 183 Anchors = [akLeft, akTop, akRight, akBottom] - ItemHeight = 13 + ItemHeight = 14 TabOrder = 2 OnClick = chklistColumnsClick end @@ -355,7 +364,7 @@ object loaddataform: Tloaddataform Width = 87 Height = 66 Align = alNone - ButtonWidth = 54 + ButtonWidth = 59 Caption = 'ToolBarColMove' Images = MainForm.VirtualImageListMain List = True diff --git a/source/loaddata.pas b/source/loaddata.pas index 77fce1d2d..899dc80e3 100644 --- a/source/loaddata.pas +++ b/source/loaddata.pas @@ -10,7 +10,7 @@ interface uses Winapi.Windows, System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls, Vcl.CheckLst, - SynRegExpr, Vcl.Buttons, Vcl.ExtCtrls, Vcl.ToolWin, Vcl.ExtDlgs, System.Math, extra_controls, + SynRegExpr, Vcl.Buttons, Vcl.ExtCtrls, Vcl.ToolWin, Vcl.ExtDlgs, System.Math, System.IOUtils, extra_controls, dbconnection, dbstructures, gnugettext; type @@ -53,6 +53,7 @@ Tloaddataform = class(TExtForm) chkLocalNumbers: TCheckBox; chkTruncateTable: TCheckBox; btnCheckAll: TToolButton; + chkKeepDialogOpen: TCheckBox; const ProgressBarSteps=100; procedure FormCreate(Sender: TObject); procedure editFilenameChange(Sender: TObject); @@ -104,6 +105,7 @@ procedure Tloaddataform.FormCreate(Sender: TObject); updownIgnoreLines.Position := AppSettings.ReadInt(asCSVImportIgnoreLines); chkLowPriority.Checked := AppSettings.ReadBool(asCSVImportLowPriority); chkLocalNumbers.Checked := AppSettings.ReadBool(asCSVImportLocalNumbers); + chkKeepDialogOpen.Checked := AppSettings.ReadBool(asCSVKeepDialogOpen); // Uncheck critical "Truncate table" checkbox, to avoid accidental data removal chkTruncateTable.Checked := False; grpDuplicates.ItemIndex := AppSettings.ReadInt(asCSVImportDuplicateHandling); @@ -172,6 +174,7 @@ procedure Tloaddataform.FormClose(Sender: TObject; var Action: TCloseAction); AppSettings.WriteInt(asCSVImportIgnoreLines, updownIgnoreLines.Position); AppSettings.WriteBool(asCSVImportLowPriority, chkLowPriority.Checked); AppSettings.WriteBool(asCSVImportLocalNumbers, chkLocalNumbers.Checked); + AppSettings.WriteBool(asCSVKeepDialogOpen, chkKeepDialogOpen.Checked); AppSettings.WriteInt(asCSVImportDuplicateHandling, grpDuplicates.ItemIndex); AppSettings.WriteInt(asCSVImportParseMethod, grpParseMethod.ItemIndex); end; @@ -300,7 +303,6 @@ procedure Tloaddataform.btnImportClick(Sender: TObject); var StartTickCount: Cardinal; i: Integer; - Warnings: TDBQuery; begin Screen.Cursor := crHourglass; StartTickCount := GetTickCount; @@ -309,9 +311,11 @@ procedure Tloaddataform.btnImportClick(Sender: TObject); // Truncate table before importing if chkTruncateTable.Checked then try FConnection.Query('TRUNCATE TABLE ' + FConnection.QuotedDbAndTableName(comboDatabase.Text, comboTable.Text)); + FConnection.ShowWarnings; except try FConnection.Query('DELETE FROM ' + FConnection.QuotedDbAndTableName(comboDatabase.Text, comboTable.Text)); + FConnection.ShowWarnings; except on E:EDbError do ErrorDialog(_('Cannot truncate table'), E.Message); @@ -329,26 +333,17 @@ procedure Tloaddataform.btnImportClick(Sender: TObject); FLineTerm := FConnection.UnescapeString(editLineTerminator.Text); FEscp := FConnection.UnescapeString(editFieldEscaper.Text); + if chkKeepDialogOpen.Checked then + ModalResult := mrNone; + try case grpParseMethod.ItemIndex of 0: ServerParse(Sender); 1: ClientParse(Sender); end; MainForm.LogSQL(FormatNumber(FRowCount)+' rows imported in '+FormatNumber((GetTickcount-StartTickCount)/1000, 3)+' seconds.'); - // SHOW WARNINGS is implemented as of MySQL 4.1.0 - if FConnection.Parameters.IsAnyMySQL and (FConnection.ServerVersionInt >= 40100) then begin - Warnings := FConnection.GetResults('SHOW WARNINGS'); - while not Warnings.Eof do begin - MainForm.LogSQL(Warnings.Col(0)+' ('+Warnings.Col(1)+'): '+Warnings.Col(2), lcError); - Warnings.Next; - end; - if Warnings.RecordCount > 0 then begin - ErrorDialog(f_('Your file was imported but the server returned %s warnings and/or notes. See the log panel for details.', [FormatNumber(Warnings.RecordCount)])); - ModalResult := mrNone; - end; - end; // Hint user if zero rows were detected in file - if (ModalResult <> mrNone) and (FRowCount = 0) then begin + if FRowCount = 0 then begin ErrorDialog(_('No rows were imported'), _('This can have several causes:')+CRLF+ _(' - File is empty')+CRLF+ @@ -373,6 +368,9 @@ procedure Tloaddataform.btnImportClick(Sender: TObject); end; end; + if ModalResult = mrNone then + btnCancel.Caption := _('Close'); + Mainform.ShowStatusMsg; MainForm.DisableProgress; Screen.Cursor := crDefault; @@ -451,6 +449,7 @@ procedure Tloaddataform.ServerParse(Sender: TObject); FConnection.Query(SQL); FRowCount := Max(FConnection.RowsAffected, 0); + FConnection.ShowWarnings; end; @@ -458,6 +457,7 @@ procedure Tloaddataform.ClientParse(Sender: TObject); var P, ContentLen, ProgressCharsPerStep, ProgressChars: Integer; IgnoreLines, ValueCount, PacketSize: Integer; + LineNum: Int64; RowCountInChunk: Int64; EnclLen, TermLen, LineTermLen: Integer; Contents: String; @@ -526,9 +526,9 @@ procedure Tloaddataform.ClientParse(Sender: TObject); for i:=0 to chkListColumns.Items.Count-1 do begin if chkListColumns.Checked[i] then // column was already counted - Inc(ValuesCounted); // increase number of counted columns + Inc(ValuesCounted); // increase number of counted columns if ValuesCounted = ValueCount then // did we count all included columns up to the current column? - Break; + Break; Inc(ColumnIndex); // if all columns (until the current column) are checked, ColumnIndex is ValueCount-1, like before this patch end; @@ -536,7 +536,7 @@ procedure Tloaddataform.ClientParse(Sender: TObject); if chkLocalNumbers.Checked and (FColumns[ColumnIndex].DataType.Category in [dtcInteger, dtcReal]) then Value := UnformatNumber(Value) else - Value := FConnection.EscapeString(Value); + Value := FConnection.EscapeString(Value, FColumns[ColumnIndex].DataType); end; SQL := SQL + Value + ', '; end; @@ -551,13 +551,13 @@ procedure Tloaddataform.ClientParse(Sender: TObject); begin if SQL = '' then Exit; - Inc(FRowCount); + Inc(LineNum); for i:=ValueCount to FColumnCount do begin Value := 'NULL'; AddValue; end; ValueCount := 0; - if FRowCount > IgnoreLines then begin + if LineNum > IgnoreLines then begin Delete(SQL, Length(SQL)-1, 2); StreamWrite(OutStream, SQL + ')'); SQL := ''; @@ -571,6 +571,8 @@ procedure Tloaddataform.ClientParse(Sender: TObject); OutStream.Read(PAnsiChar(SA)^, ChunkSize); OutStream.Size := 0; FConnection.Query(UTF8ToString(SA), False, lcScript); + Inc(FRowCount, Max(FConnection.RowsAffected, 0)); + FConnection.ShowWarnings; SQL := ''; RowCountInChunk := 0; end; @@ -607,6 +609,7 @@ procedure Tloaddataform.ClientParse(Sender: TObject); ProgressCharsPerStep := ContentLen div ProgressBarSteps; ProgressChars := 0; FRowCount := 0; + LineNum := 0; RowCountInChunk := 0; IgnoreLines := UpDownIgnoreLines.Position; ValueCount := 0; @@ -648,7 +651,6 @@ procedure Tloaddataform.ClientParse(Sender: TObject); Contents := ''; FreeAndNil(OutStream); - FRowCount := Max(FRowCount-IgnoreLines, 0); if FConnection.Parameters.IsAnyMySQL then begin FConnection.Query('/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '''') */'); @@ -663,6 +665,8 @@ procedure Tloaddataform.btnOpenFileClick(Sender: TObject); begin AppSettings.ResetPath; Dialog := TExtFileOpenDialog.Create(Self); + Dialog.DefaultFolder := ExtractFilePath(editFilename.Text); + Dialog.FileName := ExtractFileName(editFilename.Text); Dialog.AddFileType('*.csv', _('CSV files')); Dialog.AddFileType('*.txt', _('Text files')); Dialog.AddFileType('*.*', _('All files')); diff --git a/source/main.dfm b/source/main.dfm index 808c63866..3e836a323 100644 --- a/source/main.dfm +++ b/source/main.dfm @@ -440,7 +440,7 @@ object MainForm: TMainForm TreeOptions.PaintOptions = [toHotTrack, toShowButtons, toShowDropmark, toShowHorzGridLines, toShowVertGridLines, toThemeAware, toUseBlendedImages, toGhostedIfUnfocused, toUseExplorerTheme, toHideTreeLinesIfThemed] TreeOptions.SelectionOptions = [toExtendedFocus, toFullRowSelect, toMultiSelect, toRightClickSelect] OnAfterPaint = AnyGridAfterPaint - OnBeforeCellPaint = ListDatabasesBeforeCellPaint + OnBeforeCellPaint = HostListBeforeCellPaint OnBeforePaint = ListDatabasesBeforePaint OnCompareNodes = AnyGridCompareNodes OnDblClick = ListDatabasesDblClick @@ -525,11 +525,11 @@ object MainForm: TMainForm PopupMenu = popupHost ShowHint = True TabOrder = 0 - TreeOptions.MiscOptions = [toToggleOnDblClick] + TreeOptions.MiscOptions = [toGridExtensions, toToggleOnDblClick] TreeOptions.PaintOptions = [toHotTrack, toShowDropmark, toShowHorzGridLines, toShowVertGridLines, toThemeAware, toUseBlendedImages, toUseExplorerTheme] TreeOptions.SelectionOptions = [toExtendedFocus, toFullRowSelect, toRightClickSelect] OnAfterPaint = AnyGridAfterPaint - OnBeforeCellPaint = ListVariablesBeforeCellPaint + OnBeforeCellPaint = HostListBeforeCellPaint OnBeforePaint = HostListBeforePaint OnCompareNodes = AnyGridCompareNodes OnDblClick = ListVariablesDblClick @@ -587,6 +587,7 @@ object MainForm: TMainForm TreeOptions.PaintOptions = [toHotTrack, toShowDropmark, toShowHorzGridLines, toShowVertGridLines, toThemeAware, toUseBlendedImages, toUseExplorerTheme] TreeOptions.SelectionOptions = [toExtendedFocus, toFullRowSelect, toRightClickSelect] OnAfterPaint = AnyGridAfterPaint + OnBeforeCellPaint = HostListBeforeCellPaint OnBeforePaint = HostListBeforePaint OnCompareNodes = AnyGridCompareNodes OnGetText = HostListGetText @@ -1242,7 +1243,6 @@ object MainForm: TMainForm Header.MainColumn = -1 Header.Options = [hoColumnResize, hoDblClickResize, hoDrag, hoHotTrack, hoOwnerDraw, hoShowHint, hoShowImages, hoVisible, hoDisableAnimatedResize, hoAutoResizeInclCaption] IncrementalSearch = isInitializedOnly - LineStyle = lsSolid PopupMenu = popupDataGrid TabOrder = 2 TreeOptions.AutoOptions = [toAutoDropExpand, toAutoScroll, toAutoScrollOnExpand, toAutoTristateTracking, toAutoDeleteMovedNodes, toAutoChangeScale] @@ -1348,6 +1348,7 @@ object MainForm: TMainForm Gutter.RightOffset = 0 Gutter.ShowLineNumbers = True Highlighter = SynSQLSynUsed + HintMode = shmToken Options = [eoAutoIndent, eoAutoSizeMaxScrollWidth, eoDropFiles, eoGroupUndo, eoHideShowScrollbars, eoKeepCaretX, eoShowScrollHint, eoTabIndent] RightEdge = 0 TabWidth = 3 @@ -1356,13 +1357,26 @@ object MainForm: TMainForm OnReplaceText = SynMemoQueryReplaceText OnSpecialLineColors = SynMemoQuerySpecialLineColors OnStatusChange = SynMemoQueryStatusChange + OnTokenHint = SynMemoQueryTokenHint OnPaintTransient = SynMemoQueryPaintTransient OnScanForFoldRanges = SynMemoQueryScanForFoldRanges FontSmoothing = fsmNone RemovedKeystrokes = < + item + Command = ecRedo + ShortCut = 40968 + end item Command = ecDeleteWord ShortCut = 16468 + end + item + Command = ecDeleteLine + ShortCut = 16473 + end + item + Command = ecRedo + ShortCut = 24666 end> AddedKeystrokes = < item @@ -1470,7 +1484,6 @@ object MainForm: TMainForm Header.Options = [hoColumnResize, hoDblClickResize, hoDrag, hoHotTrack, hoOwnerDraw, hoShowHint, hoShowImages, hoDisableAnimatedResize, hoAutoResizeInclCaption] Header.PopupMenu = popupListHeader IncrementalSearch = isAll - LineStyle = lsSolid PopupMenu = popupDataGrid TabOrder = 1 TreeOptions.AutoOptions = [toAutoDropExpand, toAutoScroll, toAutoScrollOnExpand, toAutoTristateTracking, toAutoDeleteMovedNodes, toAutoChangeScale] @@ -1765,23 +1778,25 @@ object MainForm: TMainForm object ToolBarDonate: TToolBar Left = 863 Top = 2 - Width = 0 + Width = 67 Height = 22 - Align = alRight + Align = alNone + AutoSize = True ButtonWidth = 67 Caption = 'Donate' + EdgeInner = esNone + EdgeOuter = esNone Images = VirtualImageListMain List = True ShowCaptions = True TabOrder = 1 + Wrapable = False object btnDonate: TToolButton Left = 0 Top = 0 - Cursor = crHandPoint Hint = 'Send an arbitrary amount as donation to the author - per PayPal ' + '(also supports credit cards)' - AutoSize = True Caption = 'Donate' ImageIndex = 185 OnClick = DonateClick @@ -1856,9 +1871,18 @@ object MainForm: TMainForm object CopyItem: TMenuItem Action = actCopy end + object Copyformattedtext1: TMenuItem + Action = actCopyFormatted + end object Copywithtabstospaces1: TMenuItem Action = actCopyTabsToSpaces end + object Copycolumnnames1: TMenuItem + Action = actCopyColumnNames + end + object actCopyGridNodes1: TMenuItem + Action = actCopyGridNodes + end object PasteItem: TMenuItem Action = actPaste end @@ -1880,10 +1904,35 @@ object MainForm: TMainForm object Inverseselection1: TMenuItem Action = actSelectInverse end - object actFindInVT1: TMenuItem + end + object MainMenuDisplay: TMenuItem + Caption = 'Display' + object menuDisplaysizeofobjects1: TMenuItem + Action = actDisplayObjectSize + AutoCheck = True + end + object menuShowonlyfavorites1: TMenuItem + Action = actFavoriteObjectsOnly + AutoCheck = True + end + object menuFilterpanel1: TMenuItem Action = actFilterPanel AutoCheck = True end + object menuDisplayLogPanel1: TMenuItem + Action = actDisplayLogPanel + AutoCheck = True + end + object menuTreefilters1: TMenuItem + Action = actDisplayTreeFilters + AutoCheck = True + end + object N27: TMenuItem + Caption = '-' + end + object menuResetpaneldimensions1: TMenuItem + Action = actResetPanelDimensions + end end object MainMenuSearch: TMenuItem Caption = 'Search' @@ -2020,6 +2069,9 @@ object MainForm: TMainForm object Bulktableeditor1: TMenuItem Action = actBulkTableEdit end + object Generatedata1: TMenuItem + Action = actGenerateData + end object Launchcommandline1: TMenuItem Action = actLaunchCommandline end @@ -2047,9 +2099,6 @@ object MainForm: TMainForm object N4: TMenuItem Caption = '-' end - object Resetpaneldimensions1: TMenuItem - Action = actResetPanelDimensions - end object MenuPreferences: TMenuItem Action = actPreferences end @@ -2822,7 +2871,6 @@ object MainForm: TMainForm AutoCheck = True Caption = 'Filter panel' Hint = 'Activates the filter panel' - ImageIndex = 30 ImageName = 'icons8-find' ShortCut = 49222 OnExecute = actFilterPanelExecute @@ -2981,12 +3029,11 @@ object MainForm: TMainForm ImageName = 'icons8-comments' OnExecute = actToggleCommentExecute end - object actSynchronizeDatabase: TAction - Category = 'Export/Import' - Caption = 'Synchronize database' - ImageIndex = 27 - ImageName = 'icons8-data-backup' - OnExecute = actSynchronizeDatabaseExecute + object actGenerateData: TAction + Category = 'Tools' + Caption = 'Generate data' + ImageIndex = 130 + OnExecute = actTableToolsExecute end object actLaunchCommandline: TAction Category = 'Tools' @@ -3028,7 +3075,7 @@ object MainForm: TMainForm AutoCheck = True Caption = 'Show only favorites' Hint = 'Show only favorite tree items' - ImageIndex = 112 + ImageIndex = 113 ImageName = 'icons8-star-filled' OnExecute = actFavoriteObjectsOnlyExecute end @@ -3164,35 +3211,35 @@ object MainForm: TMainForm end object actQuickFilterFocused3: TAction Category = 'Data' - Caption = 'Quick filter: Column > Focused' + Caption = 'Quick filter: Column LIKE Focused%' ImageIndex = 61 ImageName = 'icons8-sort-right' OnExecute = QuickFilterClick end object actQuickFilterFocused4: TAction Category = 'Data' - Caption = 'Quick filter: Column < Focused' + Caption = 'Quick filter: Column LIKE %Focused' ImageIndex = 61 ImageName = 'icons8-sort-right' OnExecute = QuickFilterClick end object actQuickFilterFocused5: TAction Category = 'Data' - Caption = 'Quick filter: Column LIKE Focused%' + Caption = 'Quick filter: Column LIKE %Focused%' ImageIndex = 61 ImageName = 'icons8-sort-right' OnExecute = QuickFilterClick end object actQuickFilterFocused6: TAction Category = 'Data' - Caption = 'Quick filter: Column LIKE %Focused' + Caption = 'Quick filter: Column > Focused' ImageIndex = 61 ImageName = 'icons8-sort-right' OnExecute = QuickFilterClick end object actQuickFilterFocused7: TAction Category = 'Data' - Caption = 'Quick filter: Column LIKE %Focused%' + Caption = 'Quick filter: Column < Focused' ImageIndex = 61 ImageName = 'icons8-sort-right' OnExecute = QuickFilterClick @@ -3355,6 +3402,7 @@ object MainForm: TMainForm Category = 'Tools' Caption = 'Sequal Suggest' ImageIndex = 206 + Visible = False OnExecute = actSequalSuggestExecute end object actResetPanelDimensions: TAction @@ -3364,6 +3412,58 @@ object MainForm: TMainForm ImageIndex = 71 OnExecute = actResetPanelDimensionsExecute end + object actCopyGridNodes: TAction + Category = 'Various' + Caption = 'Copy all lines from listing or tree in CSV format' + ImageIndex = 3 + OnExecute = actCopyGridNodesExecute + end + object actQueryTable: TAction + Category = 'Database' + Caption = 'Select top 1000 rows' + Hint = 'Selects the first 1000 rows in a new query tab' + ImageIndex = 57 + OnExecute = actQueryTableExecute + end + object actDisplayObjectSize: TAction + Category = 'Various' + AutoCheck = True + Caption = 'Display size of objects' + OnExecute = actDisplayObjectSizeExecute + end + object actDisplayLogPanel: TAction + Category = 'Various' + AutoCheck = True + Caption = 'Log panel' + OnExecute = actDisplayLogPanelExecute + end + object actDisplayTreeFilters: TAction + Category = 'Various' + AutoCheck = True + Caption = 'Tree filters' + OnExecute = actDisplayTreeFiltersExecute + end + object actCopyColumnNames: TAction + Category = 'Various' + Caption = 'Copy column names' + ImageIndex = 3 + OnExecute = menuCopyColumnNamesClick + end + object actCopyFormatted: TAction + Category = 'Various' + Caption = 'Copy formatted text' + Hint = + 'Copies selected text with formatting from current editor to clip' + + 'board' + ImageIndex = 3 + OnExecute = actCopyFormattedExecute + end + object actDataEditWithoutLookup: TAction + Category = 'Data' + Caption = 'Edit value without foreign key lookup' + ImageIndex = 58 + OnExecute = actDataEditWithoutLookupExecute + end end object menuConnections: TPopupMenu AutoHotkeys = maManual @@ -3401,6 +3501,9 @@ object MainForm: TMainForm object menuEmptyTables: TMenuItem Action = actEmptyTables end + object Selecttop1000rows1: TMenuItem + Action = actQueryTable + end object Runroutines1: TMenuItem Action = actRunRoutines end @@ -3433,7 +3536,7 @@ object MainForm: TMainForm end end object menuClearDataTabFilter: TMenuItem - Caption = 'Clear data tab filter' + Caption = 'Clear data tab filter and sort order' OnClick = menuClearDataTabFilterClick end object N17: TMenuItem @@ -3451,6 +3554,9 @@ object MainForm: TMainForm object menuBulkTableEdit: TMenuItem Action = actBulkTableEdit end + object Generatedata2: TMenuItem + Action = actGenerateData + end object N5a: TMenuItem Caption = '-' end @@ -3471,8 +3577,8 @@ object MainForm: TMainForm AutoCheck = True end object menuShowSizeColumn: TMenuItem - Caption = 'Display size of objects' - OnClick = menuShowSizeColumnClick + Action = actDisplayObjectSize + AutoCheck = True end object menuAutoExpand: TMenuItem Caption = 'Auto expand on click' @@ -3603,6 +3709,9 @@ object MainForm: TMainForm ImageIndex = 28 OnClick = InsertValue end + object menuDataEditWithoutLookup: TMenuItem + Action = actDataEditWithoutLookup + end object N11: TMenuItem Caption = '-' end @@ -3893,12 +4002,9 @@ object MainForm: TMainForm end object popupListHeader: TVTHeaderPopupMenu Images = VirtualImageListMain + OnPopup = popupListHeaderPopup Left = 424 Top = 208 - object menuToggleAll: TMenuItem - Caption = 'Toggle visibility of all columns' - OnClick = menuToggleAllClick - end end object SynCompletionProposal: TSynCompletionProposal Options = [scoLimitToMatchedText, scoUseInsertList, scoUsePrettyText, scoUseBuiltInTimer, scoEndCharCompletion, scoCompleteWithTab, scoCompleteWithEnter] @@ -4041,6 +4147,12 @@ object MainForm: TMainForm ImageIndex = 52 OnClick = menuLoadSnippetClick end + object menuRenameSnippet: TMenuItem + Caption = 'Rename' + Enabled = False + ImageIndex = 58 + OnClick = menuRenameSnippetClick + end object menuDeleteSnippet: TMenuItem Caption = 'Delete ...' Enabled = False @@ -4060,6 +4172,7 @@ object MainForm: TMainForm object menuClearQueryHistory: TMenuItem Caption = 'Clear query history ...' Enabled = False + ImageIndex = 26 OnClick = menuClearQueryHistoryClick end object menuQueryHelpersGenerateSelect: TMenuItem @@ -17252,6 +17365,76 @@ object MainForm: TMainForm 3CB5F304692F364254B75D5235702FC6148542F1398690FF029A8D9B27B85043 410000000049454E44AE426082} end> + end + item + Name = 'icons8-key-100-pink' + SourceImages = < + item + Image.Data = { + 89504E470D0A1A0A0000000D494844520000006400000064080600000070E295 + 54000000017352474200AECE1CE90000000467414D410000B18F0BFC61050000 + 00097048597300000EC100000EC101B8916BED0000001874455874536F667477 + 617265005061696E742E4E455420352E312E36FCD1C7AF000000B66558496649 + 492A000800000005001A010500010000004A0000001B01050001000000520000 + 0028010300010000000200000031010200100000005A00000069870400010000 + 006A00000000000000D9760100E8030000D9760100E80300005061696E742E4E + 455420352E312E3600030000900700040000003032333001A003000100000001 + 00000005A0040001000000940000000000000002000100020004000000523938 + 00020007000400000030313030000000008CF53740E4EAF7DB00000684494441 + 54785EED9C5F8C1D6519879FF7DB026B410B3456531A2818439A81201AF14C51 + 88240AD1BB12B4DD6E132F4C30263424C6987883E28531D1C4849AA0912BDA6D + 43421B2F309AF897A87310A912185B8D01C28242CB22E05AAAED7EAF177B9A6E + DF0BB3E7CC3BA7DF9C9927996CF27BBF3DE74C9ECC9CF3FD1BE8E8E8E8E8E8E8 + E8E8F0476C90128F64FDCB227A45844D0A1B053609B256D177016B027269442F + 002E19FCCB62404E45F40DE0B4206F297A029807FE11E0A580BCFC99B2F74FF3 + 56C970DE85ECCDFA6BD7C096886E01B600D70EFE5E1D9077D8F61E44F46DE079 + E0087014381A9023A7E1C86CD93B61DB8F93B10AE9678FCAF36CEC297C04F810 + F041E0DA804CD9B6E783882E017F010E034F093C71357FEFF7CA3BD5B6AD8BB1 + 083998F52F3C89DE0D7C292057D97ACA44F405E03BD3C80FB695BDFFDABA37B5 + 0BD99F15372A3C1C90CCD69A44449F89B06BB6CC9FB6354F6A15B22F2B3E25F0 + 48402EB6B52612D145E0AE9932FF89AD79519B90FD59912BFC2C206B6DADC944 + F484C0AD3BCAFC0FB6E6412D420E64C5C5119E0DC8665B9B0422FA5C44AEAFE3 + 1759B0810711EE9D54190001B9660A76DBDC03F72BE481AC1FD6A3F301D9686B + 934444E71790CDF794BD686B5570173297153707E43736AF4A44FF031C078E05 + 4423FAE6F2C5C8A9802C0EDABC1358038480AC8BA8001B800D01B9D0BE665522 + FAD19932FFADCDAB508790FB02F2359BAF96881E077E073C093C2930BF04AFCE + 96F9EBB6ED30ECCD8ACB81F706D804DC047C18C803F26EDB76B544F4BE9932BF + DFE655A843C8E301F998CD5743441F9E463E3F8E0E18673BAC3F0CC82E5B5B0D + 117D7CA6CC6FB579155CBFD41FCA8A8B581E161995625C320006EF55D87C086E + DA9BF52FB061155C85AC85EBEBB857A74A40A6A7D0EB6C5E0557210A37DA6CD2 + D1E54152375C85D04221C0076C50056F21D7D860482EB7C118586F8321A97ACE + E7E02DE47D3618924FDA600CDC618321711D917013F240D60FC095361F8680DC + 329715B336AF8BB9ACD815909B6D3E24AE42DCFA2107B26203C8AB361F96889E + 06BE1960CFF6323F66EB1EECCD8AF784E5B1A8AF78CC569E46D757EDB89EC14D + C8FEACBF5996E7A93D7933A2AE63450109C03A9B57E4AAED65EF451B8E82DB2D + 6B308EE4CDBA805CE679D420C3F5DCDD8484B34B715A8782DB249C9B1071B817 + 379529E4229B8D8A9B1085459BB588B76D302A6E42585E21D84A14FE6DB35171 + 1312A0B542229A9E9088BC66B3B6A0C882CD46C54DC88EB27732A2AFD87CD289 + E882E7EA133721035EB0C1A41310D773F616F29C0D269D88BA8E4E780BF9B30D + 5AC0511B54C15588402DCB2B13E7B00DAAE02DE48F369B7402F294CDAAE02A64 + 7B991FF3BEA7A64C445FF11AE53D83AB90013FB7C104F32B1B54A513528D5FD8 + A02AEE42D6203F8DE8299B4F1A115505F78D3BEE42065B8E1FB3F904F2CB9D65 + 3E6FC3AAB80B6179F4F31BDE53AFA921E0BAC8FA0CB508D959E687816FDB7C52 + 88E8433BCAFCD736F7A0162100D3C85723FA239B379D883E11915A764F51A790 + 6D656F29227745744F44C7B6F1BE4E22FAE388DCEE39BA6B715B06F4FF98CB8A + 5B04B95FC0752FC5B888E83181AFBF863CE8BD85CD32162167D89715EF17F874 + 40B646746B40AEB06D52607045FF0DF83DF09820877694BD93B65D1D8C5588E5 + 40D6BF32A25B811B800CB80ED81C90B17DAE882E0C466C8F002570780AFEF4D9 + 327FCBB61D07633BF1D57230EB5F7212FD5640BE686B9E44742EC2BDB3657EDC + D6CE27B57DA98FCAB6B2B728C8AA3A5C11E5758E9F734456FDFBE1C5D46490A2 + 906159E2D43947D369BC9049A31392189D90C4E88424462B850C3A7EB53E196E + 545A276420E39E99323F606B29D02A212B647CCFD652A135429A2083360901BE + 9FBA0C5A26E473735951F52101B5D31A210199060EA52EA53542688894560961 + 85947D59719BADA540EB84309022F0099BA7402B85A44C272431DA2C64D5538B + E3A49542225A06F8AECD53A0754206326EABEB595C55699590D465D02621112D + 237C3C6519B44908F0D214F22F1BA6466B8404E476450FEDCFFAD3B69612AD11 + C2B2943B5297D22A2134404AEB847056CA976D9E02AD1432C0EDC1959EB45948 + 9274421223492111FD6B9DFB12BB857243B2B3CC0F02BBEB903278CDDDDD42B9 + 219929F33DDE5256C8D8636BA990AC109CA5344106A90BC1494A5364D0042154 + 94D22419344508234A699A0C9A248421A534510629EE535F0D7359F105E04E45 + A7DE60E18695B54B59FFB4204BC0A33365FEE0CA5A4747474747474747475BF9 + 1F7FA73D2F7ABAF21F0000000049454E44AE426082} + end> end> Left = 593 Top = 339 @@ -25545,6 +25728,34 @@ object MainForm: TMainForm 3CB5F304692F364254B75D5235702FC6148542F1398690FF029A8D9B27B85043 410000000049454E44AE426082} end> + end + item + Name = 'key_vector' + SourceImages = < + item + Image.Data = { + 89504E470D0A1A0A0000000D49484452000000100000001008060000001FF3FF + 61000000017352474200AECE1CE90000000467414D410000B18F0BFC61050000 + 00097048597300000EC300000EC301C76FA8640000001874455874536F667477 + 617265005061696E742E4E455420352E312E36FCD1C7AF000000B66558496649 + 492A000800000005001A010500010000004A0000001B01050001000000520000 + 0028010300010000000200000031010200100000005A00000069870400010000 + 006A00000000000000600000000100000060000000010000005061696E742E4E + 455420352E312E3600030000900700040000003032333001A003000100000001 + 00000005A0040001000000940000000000000002000100020004000000523938 + 0002000700040000003031303000000000C2087FD6D5255A4D0000013B494441 + 54384FDD8C4B4B026118858F635184BCD885124BDA77A1887E84AD8330C995E0 + 2E68DBB2458B685FEDFA01B58DA06D57BB29220C06038A1853605333A3BDC3E8 + F7B58B61C4DAFBEC0EE73907E86D3462A542BC6745DC078D38EDEFFFA544BC25 + E22CC59929C5ACA867C988FB9DAE68C419B1F4DD163B552992A67452425667B8 + 714AEABAD753BCC14B085841C2549077815803FD2DC6E4DCFBD0C2C0F4A1D7EB + 7AC0808A1705786D018F4D205F016E9BA8C1097ABD8E038D78D988B8F7D1D160 + 02D7365062A0EC007600575F139C778A19FFE6970AF1468DF8596C7F4811294A + 41055920DDBE091B7A366CE90774B7EADF04BCE133E6E628632E62BF0EA08D52 + DF70F3BCF5B419C4FC490883489BE396D7EFC08ABA5991D4A408AB521DD1AD23 + BA48F99D3FC9516DB73C65BCE5C6F4F2315DAEF9FB1EE50766138FD293CD4ECC + 0000000049454E44AE426082} + end> end> Left = 689 Top = 339 @@ -25560,6 +25771,7 @@ object MainForm: TMainForm Left = 352 Top = 320 object menuQueryExactRowCount: TMenuItem + AutoCheck = True Caption = 'Query exact row count' OnClick = menuQueryExactRowCountClick end diff --git a/source/main.pas b/source/main.pas index eb9afa857..b332ae053 100644 --- a/source/main.pas +++ b/source/main.pas @@ -21,7 +21,8 @@ interface JumpList, System.Actions, System.UITypes, Vcl.Imaging.pngimage, System.ImageList, Vcl.Styles.Utils.Forms, Vcl.VirtualImageList, Vcl.BaseImageCollection, Vcl.ImageCollection, System.IniFiles, extra_controls, - SynEditCodeFolding, SynEditStrConst, texteditor, System.Character, generic_types, Sequal.Suggest; + SynEditCodeFolding, SynEditStrConst, texteditor, System.Character, generic_types, Sequal.Suggest, + VirtualTrees.BaseAncestorVCL, VirtualTrees.BaseTree, VirtualTrees.AncestorVCL; type @@ -52,15 +53,19 @@ TResultTab = class(TObject) Results: TDBQuery; Grid: TVirtualStringTree; FilterText: String; + private + FTabIndex: Integer; public constructor Create(AOwner: TQueryTab); destructor Destroy; override; + property TabIndex: Integer read FTabIndex; end; TResultTabs = TObjectList; TQueryTab = class(TComponent) const IdentBackupFilename = 'BackupFilename'; IdentFilename = 'Filename'; + IdentFileEncoding = 'FileEncoding'; IdentCaption = 'Caption'; IdentPid = 'pid'; IdentEditorHeight = 'EditorHeight'; @@ -81,6 +86,7 @@ TQueryTab = class(TComponent) FLastChange: TDateTime; FDirectoryWatchNotficationRunning: Boolean; FErrorLine: Integer; + FFileEncoding: String; procedure SetMemoFilename(Value: String); procedure SetQueryRunning(Value: Boolean); procedure TimerLastChangeOnTimer(Sender: TObject); @@ -132,6 +138,7 @@ TQueryTab = class(TComponent) destructor Destroy; override; class function GenerateUid: String; property ErrorLine: Integer read FErrorLine write SetErrorLine; + property FileEncoding: String read FFileEncoding write FFileEncoding; end; TQueryTabList = class(TObjectList) public @@ -190,11 +197,14 @@ TQueryHistoryItemComparer = class(TComparer) end; TMainForm = class(TExtForm) + actDataEditWithoutLookup: TAction; MainMenu1: TMainMenu; MainMenuFile: TMenuItem; FileNewItem: TMenuItem; MainMenuHelp: TMenuItem; FollowForeignKey: TMenuItem; + menuDataEditWithoutLookup: TMenuItem; + menuRenameSnippet: TMenuItem; N1: TMenuItem; FileExitItem: TMenuItem; menuAbout: TMenuItem; @@ -496,7 +506,6 @@ TMainForm = class(TExtForm) pnlRight: TPanel; btnCloseFilterPanel: TSpeedButton; actFilterPanel: TAction; - actFindInVT1: TMenuItem; TimerFilterVT: TTimer; actFindTextOnServer: TAction; actFindTextOnServer1: TMenuItem; @@ -577,7 +586,6 @@ TMainForm = class(TExtForm) actCancelOperation: TAction; actToggleComment: TAction; Uncomment1: TMenuItem; - actSynchronizeDatabase: TAction; Disconnect1: TMenuItem; N4: TMenuItem; ImportCSVfile1: TMenuItem; @@ -769,7 +777,6 @@ TMainForm = class(TExtForm) Copywithtabstospaces1: TMenuItem; Movelinedown1: TMenuItem; Movelineup1: TMenuItem; - menuToggleAll: TMenuItem; menuCloseTabOnDblClick: TMenuItem; Undo1: TMenuItem; actSequalSuggest: TAction; @@ -783,10 +790,32 @@ TMainForm = class(TExtForm) ToolBarDonate: TToolBar; btnDonate: TToolButton; actResetPanelDimensions: TAction; - Resetpaneldimensions1: TMenuItem; popupApplyFilter: TPopupMenu; menuAlwaysGenerateFilter: TMenuItem; + actGenerateData: TAction; + Generatedata1: TMenuItem; + Generatedata2: TMenuItem; + actCopyGridNodes: TAction; + actCopyGridNodes1: TMenuItem; + actQueryTable: TAction; + Selecttop1000rows1: TMenuItem; + MainMenuDisplay: TMenuItem; + actDisplayObjectSize: TAction; + menuDisplaysizeofobjects1: TMenuItem; + menuShowonlyfavorites1: TMenuItem; + menuFilterpanel1: TMenuItem; + menuResetpaneldimensions1: TMenuItem; + actDisplayLogPanel: TAction; + actDisplayTreeFilters: TAction; + menuDisplayLogPanel1: TMenuItem; + menuTreefilters1: TMenuItem; + N27: TMenuItem; + actCopyColumnNames: TAction; + Copycolumnnames1: TMenuItem; + actCopyFormatted: TAction; + Copyformattedtext1: TMenuItem; procedure actCreateDBObjectExecute(Sender: TObject); + procedure actDataEditWithoutLookupExecute(Sender: TObject); procedure menuConnectionsPopup(Sender: TObject); procedure actExitApplicationExecute(Sender: TObject); procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA; @@ -805,6 +834,7 @@ TMainForm = class(TExtForm) procedure actTableToolsExecute(Sender: TObject); procedure actPrintListExecute(Sender: TObject); procedure actCopyTableExecute(Sender: TObject); + procedure popupListHeaderPopup(Sender: TObject); procedure ShowStatusMsg(Msg: String=''; PanelNr: Integer=6); procedure actExecuteQueryExecute(Sender: TObject); procedure actCreateDatabaseExecute(Sender: TObject); @@ -869,7 +899,7 @@ TMainForm = class(TExtForm) procedure SynMemoQueryDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); procedure SynMemoQueryDragDrop(Sender, Source: TObject; X, Y: Integer); - procedure SynMemoQueryDropFiles(Sender: TObject; X, Y: Integer; AFiles: TStrings); + procedure SynMemoQueryDropFiles(Sender: TObject; X, Y: Integer; AFiles: TUnicodeStrings); procedure popupHostPopup(Sender: TObject); procedure popupDBPopup(Sender: TObject); procedure popupDataGridPopup(Sender: TObject); @@ -896,6 +926,7 @@ TMainForm = class(TExtForm) TColumnIndex; NewText: String); procedure AnyGridPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); + procedure menuRenameSnippetClick(Sender: TObject); procedure menuDeleteSnippetClick(Sender: TObject); procedure menuExploreClick(Sender: TObject); procedure menuInsertAtCursorClick(Sender: TObject); @@ -944,7 +975,7 @@ TMainForm = class(TExtForm) procedure AnyGridAfterCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect); - procedure menuShowSizeColumnClick(Sender: TObject); + procedure actDisplayObjectSizeExecute(Sender: TObject); procedure AnyGridBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); @@ -984,8 +1015,7 @@ TMainForm = class(TExtForm) procedure actCloseQueryTabExecute(Sender: TObject); procedure menuCloseQueryTabClick(Sender: TObject); procedure CloseQueryTab(PageIndex: Integer); - procedure CloseButtonOnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); - procedure CloseButtonOnMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); + procedure CloseButtonOnClick(Sender: TObject); function GetMainTabAt(X, Y: Integer): Integer; procedure FixQueryTabCloseButtons; function GetOrCreateEmptyQueryTab(DoFocus: Boolean): TQueryTab; @@ -1027,9 +1057,6 @@ TMainForm = class(TExtForm) procedure ListDatabasesInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); procedure ListDatabasesGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); - procedure ListDatabasesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; - Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; - var ContentRect: TRect); procedure menuFetchDBitemsClick(Sender: TObject); procedure ListDatabasesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex); @@ -1079,7 +1106,6 @@ TMainForm = class(TExtForm) procedure actCancelOperationExecute(Sender: TObject); procedure AnyGridChange(Sender: TBaseVirtualTree; Node: PVirtualNode); procedure actToggleCommentExecute(Sender: TObject); - procedure actSynchronizeDatabaseExecute(Sender: TObject); procedure DBtreeBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); @@ -1100,9 +1126,6 @@ TMainForm = class(TExtForm) procedure editDatabaseTableFilterMenuClick(Sender: TObject); procedure editDatabaseTableFilterExit(Sender: TObject); procedure menuClearDataTabFilterClick(Sender: TObject); - procedure ListVariablesBeforeCellPaint(Sender: TBaseVirtualTree; - TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; - CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); procedure actUnixTimestampColumnExecute(Sender: TObject); procedure PopupQueryLoadPopup(Sender: TObject); procedure DonateClick(Sender: TObject); @@ -1175,6 +1198,7 @@ TMainForm = class(TExtForm) procedure FormBeforeMonitorDpiChanged(Sender: TObject; OldDPI, NewDPI: Integer); procedure menuToggleAllClick(Sender: TObject); + procedure menuCopyColumnNamesClick(Sender: TObject); procedure FormAfterMonitorDpiChanged(Sender: TObject; OldDPI, NewDPI: Integer); procedure menuCloseTabOnDblClickClick(Sender: TObject); @@ -1191,12 +1215,17 @@ TMainForm = class(TExtForm) procedure menuTabsInMultipleLinesClick(Sender: TObject); procedure actResetPanelDimensionsExecute(Sender: TObject); procedure menuAlwaysGenerateFilterClick(Sender: TObject); + procedure SynMemoQueryTokenHint(Sender: TObject; Coords: TBufferCoord; + const Token: string; TokenType: Integer; Attri: TSynHighlighterAttributes; + var HintText: string); + procedure actCopyGridNodesExecute(Sender: TObject); + procedure actQueryTableExecute(Sender: TObject); + procedure actDisplayLogPanelExecute(Sender: TObject); + procedure actDisplayTreeFiltersExecute(Sender: TObject); + procedure actCopyFormattedExecute(Sender: TObject); private // Executable file details - FAppVerMajor: Integer; - FAppVerMinor: Integer; - FAppVerRelease: Integer; - FAppVerRevision: Integer; + FAppVerMajor, FAppVerMinor, FAppVerRelease, FAppVerRevision: Word; FAppVersion: String; FLastHintMousepos: TPoint; @@ -1238,6 +1267,7 @@ TMainForm = class(TExtForm) FCreateDatabaseDialog: TCreateDatabaseForm; FTableToolsDialog: TfrmTableTools; FGridEditFunctionMode: Boolean; + FDataEditWithoutLookup: Boolean; FClipboardHasNull: Boolean; FTimeZoneOffset: Integer; FGridCopying: Boolean; @@ -1249,6 +1279,7 @@ TMainForm = class(TExtForm) FLastPortableSettingsSave: Cardinal; FLastAppSettingsWrites: Integer; FFormatSettings: TFormatSettings; + FDefaultHintFontName: String; FActionList1DefaultCaptions: TStringList; FActionList1DefaultHints: TStringList; FEditorCommandStrings: TStringList; @@ -1256,7 +1287,7 @@ TMainForm = class(TExtForm) FMatchingBraceForegroundColor: TColor; FMatchingBraceBackgroundColor: TColor; FSynEditInOnPaintTransient: Boolean; - FExactRowCountMode: Boolean; + //FHelpData: TSimpleKeyValuePairs; // Host subtabs backend structures FHostListResults: TDBQueryList; @@ -1289,6 +1320,7 @@ TMainForm = class(TExtForm) procedure SetSnippetFilenames; function TreeClickHistoryPrevious(MayBeNil: Boolean=False): PVirtualNode; procedure OperationRunning(Runs: Boolean); + procedure OpenQueryFiles(Filenames: TStrings; Encoding: TEncoding; ForceRun: Boolean); function RunQueryFiles(Filenames: TStrings; Encoding: TEncoding; ForceRun: Boolean): Boolean; function RunQueryFile(Filename: String; Encoding: TEncoding; Conn: TDBConnection; ProgressDialog: IProgressDialog; FilesizeSum: Int64; var CurrentPosition: Int64): Boolean; @@ -1298,6 +1330,7 @@ TMainForm = class(TExtForm) function InitTabsIniFile: TIniFile; procedure StoreTabs; function RestoreTabs: Boolean; + procedure SetHintFontByControl(Control: TWinControl=nil); public QueryTabs: TQueryTabList; ActiveObjectEditor: TDBObjectEditor; @@ -1326,11 +1359,13 @@ TMainForm = class(TExtForm) TaskbarList3: ITaskbarList3; TaskbarList4: ITaskbarList4; - property AppVerRevision: Integer read FAppVerRevision; + property AppVerRevision: Word read FAppVerRevision; property AppVersion: String read FAppVersion; property Connections: TDBConnectionList read FConnections; property Delimiter: String read FDelimiter write SetDelimiter; property FocusedTables: TDBObjectList read FFocusedTables; + function GetAlternatingRowBackground(Node: PVirtualNode): TColor; + procedure PaintAlternatingRowBackground(TargetCanvas: TCanvas; Node: PVirtualNode; CellRect: TRect); procedure PaintColorBar(Value, Max: Extended; TargetCanvas: TCanvas; CellRect: TRect); procedure CallSQLHelpWithKeyword( keyword: String ); procedure AddOrRemoveFromQueryLoadHistory(Filename: String; AddIt: Boolean; CheckIfFileExists: Boolean); @@ -1398,8 +1433,14 @@ TMainForm = class(TExtForm) const CheckedStates = [csCheckedNormal, csCheckedPressed, csMixedNormal, csMixedPressed]; - ErrorLineForeground: TColor = $00FFFFFF; - ErrorLineBackground: TColor = $00000080; + ErrorLineForeground: TColor = $00000000; + ErrorLineBackground: TColor = $00D2B7FF; + WarningLineForeground: TColor = $00000000; + WarningLineBackground: TColor = $00B7CDFF; + NoteLineForeground: TColor = $00000000; + NoteLineBackground: TColor = $00D3F7FF; + InfoLineForeground: TColor = $00000000; + InfoLineBackground: TColor = $00C6FFEC; {$I const.inc} @@ -1425,7 +1466,7 @@ procedure TMainForm.ShowStatusMsg(Msg: String=''; PanelNr: Integer=6); Msg := _(SIdle); if Msg <> StatusBar.Panels[PanelNr].Text then begin StatusBar.Panels[PanelNr].Text := Msg; - if (PanelNr = 6) and (not IsWine) then begin + if (PanelNr = 6) and IsWindow(StatusBar.Handle) then begin // Immediately repaint this special panel, as it holds critical update messages, // while avoiding StatusBar.Repaint which refreshes all panels SendMessage(StatusBar.Handle, SB_GETRECT, PanelNr, Integer(@PanelRect)); @@ -1478,8 +1519,10 @@ procedure TMainForm.StatusBarDrawPanel(StatusBar: TStatusBar; Panel: TStatusPane 2: ImageIndex := 149; 3: begin Conn := ActiveConnection; - if Conn <> nil then + if Conn <> nil then try ImageIndex := Conn.Parameters.ImageIndex; + except + end; end; 5: ImageIndex := 190; 6: begin @@ -1507,6 +1550,7 @@ procedure TMainForm.StatusBarMouseMove(Sender: TObject; Shift: TShiftState; X, Y i: Integer; Infos: TStringList; HintText: String; + Conn: TDBConnection; begin // Display various server, client and connection related details in a hint if IsWine then @@ -1527,8 +1571,9 @@ procedure TMainForm.StatusBarMouseMove(Sender: TObject; Shift: TShiftState; X, Y Exit; FLastHintControlIndex := i; if FLastHintControlIndex = 3 then begin - if ActiveConnection <> nil then begin - Infos := ActiveConnection.ConnectionInfo; + Conn := ActiveConnection; + if (Conn <> nil) and (not Conn.IsLockedByThread) then begin + Infos := Conn.ConnectionInfo; HintText := ''; for i:=0 to Infos.Count-1 do begin HintText := HintText + Infos.Names[i] + ': ' + StrEllipsis(Infos.ValueFromIndex[i], 200) + CRLF; @@ -1702,6 +1747,11 @@ procedure TMainForm.actGridEditFunctionExecute(Sender: TObject); ActiveGrid.EditNode(ActiveGrid.FocusedNode, ActiveGrid.FocusedColumn); end; +procedure TMainForm.actDataEditWithoutLookupExecute(Sender: TObject); +begin + FDataEditWithoutLookup := True; + DataGrid.EditNode(DataGrid.FocusedNode, DataGrid.FocusedColumn); +end; procedure TMainForm.StoreLastSessions; var @@ -1768,8 +1818,9 @@ procedure TMainForm.FormBeforeMonitorDpiChanged(Sender: TObject; OldDPI, Factor: Extended; begin // Moving window to different screen or user changed DPI setting for current screen - Factor := 100 / PixelsPerInch * NewDPI; + Factor := 100 / PixelsPerInchDesigned * NewDPI; LogSQL(f_('Scaling controls to screen DPI: %d%%', [Round(Factor)])); + //LogSQL('PixelsPerInchDesigned:'+PixelsPerInchDesigned.ToString+' OldDPI:'+OldDPI.ToString+' NewDPI:'+NewDPI.ToString); end; @@ -1866,11 +1917,6 @@ procedure TMainForm.FormCreate(Sender: TObject); QueryTab: TQueryTab; Action, CopyAsAction: TAction; ExportFormat: TGridExportFormat; - dwInfoSize, // Size of VERSIONINFO structure - dwVerSize, // Size of Version Info Data - dwWnd: DWORD; // Handle for the size call. - FI: PVSFixedFileInfo; // Delphi structure; see WINDOWS.PAS - ptrVerBuf: Pointer; CopyAsMenu, CommandMenu: TMenuItem; TZI: TTimeZoneInformation; dti: TDBDatatypeCategoryIndex; @@ -1908,16 +1954,8 @@ procedure TMainForm.FormCreate(Sender: TObject); end; // Detect version - dwInfoSize := GetFileVersionInfoSize(PChar(Application.ExeName), dwWnd); - GetMem(ptrVerBuf, dwInfoSize); - GetFileVersionInfo(PChar(Application.ExeName), dwWnd, dwInfoSize, ptrVerBuf); - VerQueryValue(ptrVerBuf, '\', Pointer(FI), dwVerSize ); - FAppVerMajor := HiWord(FI.dwFileVersionMS); - FAppVerMinor := LoWord(FI.dwFileVersionMS); - FAppVerRelease := HiWord(FI.dwFileVersionLS); - FAppVerRevision := LoWord(FI.dwFileVersionLS); + GetExecutableVersion(Application.ExeName, FAppVerMajor, FAppVerMinor, FAppVerRelease, FAppVerRevision); FAppVersion := Format('%d.%d.%d.%d', [FAppVerMajor, FAppVerMinor, FAppVerRelease, FAppVerRevision]); - FreeMem(ptrVerBuf); // Taskbar button interface for Windows 7 // Possibly fails. See http://www.heidisql.com/forum.php?t=22451 @@ -2006,16 +2044,6 @@ procedure TMainForm.FormCreate(Sender: TObject); // Enable auto completion in data tab, filter editor SynCompletionProposal.AddEditor(SynMemoFilter); - // Fix node height on Virtual Trees for current DPI settings - FixVT(DBTree); - FixVT(ListDatabases); - FixVT(ListVariables); - FixVT(ListStatus); - FixVT(ListProcesses); - FixVT(ListCommandStats); - FixVT(ListTables); - FixVT(treeQueryHelpers); - // Window position Left := AppSettings.ReadInt(asMainWinLeft); Top := AppSettings.ReadInt(asMainWinTop); @@ -2056,8 +2084,6 @@ procedure TMainForm.FormCreate(Sender: TObject); LogToFile := AppSettings.ReadBool(asLogToFile); if AppSettings.ReadBool(asLogHorizontalScrollbar) then actLogHorizontalScrollbar.Execute; - if AppSettings.ReadBool(asFavoriteObjectsOnly) then - actFavoriteObjectsOnly.Execute; // Data-Font: ApplyFontToGrids; @@ -2077,10 +2103,14 @@ procedure TMainForm.FormCreate(Sender: TObject); DataGridTable := nil; FActiveDbObj := nil; - // Database tree options + // Display options, and database tree options actGroupObjects.Checked := AppSettings.ReadBool(asGroupTreeObjects); - if AppSettings.ReadBool(asDisplayObjectSizeColumn) then - menuShowSizeColumn.Click; + actDisplayObjectSize.Checked := AppSettings.ReadBool(asDisplayObjectSizeColumn); + actDisplayObjectSizeExecute(nil); + actDisplayLogPanel.Checked := AppSettings.ReadBool(asDisplayLogPanel); + actDisplayLogPanelExecute(nil); + actDisplayTreeFilters.Checked := AppSettings.ReadBool(asDisplayTreeFilters); + actDisplayTreeFiltersExecute(nil); if AppSettings.ReadBool(asAutoExpand) then menuAutoExpand.Click; if AppSettings.ReadBool(asDoubleClickInsertsNodeText) then @@ -2096,7 +2126,10 @@ procedure TMainForm.FormCreate(Sender: TObject); FActionList1DefaultHints.Insert(i, Action.Hint); end; - // Size of completion proposal window + // Completion proposal window + // The proposal form gets scaled a second time when it shows its form with Scaled=True. + // We already store and restore the dimensions DPI aware. + SynCompletionProposal.Form.Scaled := False; SynCompletionProposal.TimerInterval := AppSettings.ReadInt(asCompletionProposalInterval); SynCompletionProposal.Width := AppSettings.ReadInt(asCompletionProposalWidth); SynCompletionProposal.NbLinesInWindow := AppSettings.ReadInt(asCompletionProposalNbLinesInWindow); @@ -2138,7 +2171,7 @@ procedure TMainForm.FormCreate(Sender: TObject); FGridCopying := False; FGridPasting := False; - FileEncodings := Explode(',', _('Auto detect (may fail)')+',ANSI,ASCII,Unicode,Unicode Big Endian,UTF-8,UTF-7'); + FileEncodings := Explode(',', _('Auto detect (may fail)')+',ANSI,ASCII,Unicode,Unicode Big Endian,UTF-8,UTF-7,UTF-8-BOM'); // Detect timezone offset in seconds, once case GetTimeZoneInformation(TZI) of @@ -2149,6 +2182,16 @@ procedure TMainForm.FormCreate(Sender: TObject); end; FTimeZoneOffset := FTimeZoneOffset * 60; + // Fix node height on Virtual Trees + FixVT(DBTree); + FixVT(ListDatabases); + FixVT(ListVariables); + FixVT(ListStatus); + FixVT(ListProcesses); + FixVT(ListCommandStats); + FixVT(ListTables); + FixVT(treeQueryHelpers); + // Set noderoot for query helpers box treeQueryHelpers.RootNodeCount := 7; @@ -2162,6 +2205,7 @@ procedure TMainForm.FormCreate(Sender: TObject); FLastPortableSettingsSave := 0; FLastAppSettingsWrites := 0; FFormatSettings := TFormatSettings.Create('en-US'); + FDefaultHintFontName := Screen.HintFont.Name; // Now we are free to use certain methods, which are otherwise fired too early MainFormCreated := True; @@ -2190,11 +2234,10 @@ procedure TMainForm.AfterFormCreate; StatsCall: THttpDownload; SessionPaths: TStringlist; DlgResult: TModalResult; - Tab: TQueryTab; SessionManager: TConnForm; begin - // Do an updatecheck if checked in settings if AppSettings.ReadBool(asUpdatecheck) then begin + // Do an updatecheck if checked in settings LastUpdatecheck := StrToDateTimeDef(AppSettings.ReadString(asUpdatecheckLastrun), DateTimeNever); UpdatecheckInterval := AppSettings.ReadInt(asUpdatecheckInterval); if DaysBetween(Now, LastUpdatecheck) >= UpdatecheckInterval then begin @@ -2266,6 +2309,9 @@ procedure TMainForm.AfterFormCreate; // Delete scheduled task from previous if RunFrom = 'scheduler' then begin DeleteRestartTask; + if HasDonated(False) <> nbTrue then begin + apphelpers.ShellExec(APPDOMAIN + 'after-updatecheck?rev=' + AppVerRevision.ToString); + end; end; if ConnectionParams <> nil then begin @@ -2322,14 +2368,7 @@ procedure TMainForm.AfterFormCreate; end; // Load SQL file(s) by command line - if not RunQueryFiles(FileNames, nil, false) then begin - for i:=0 to FileNames.Count-1 do begin - Tab := GetOrCreateEmptyQueryTab(False); - Tab.LoadContents(FileNames[i], True, nil); - if i = FileNames.Count-1 then - SetMainTab(Tab.TabSheet); - end; - end; + OpenQueryFiles(FileNames, nil, False); MainFormAfterCreateDone := True; end; @@ -2343,10 +2382,7 @@ function TMainForm.InitTabsIniFile: TIniFile; begin // Try to open tabs.ini for writing or reading // Taking multiple application instances into account - if AppSettings.PortableMode then - TabsIniFilename := ExtractFilePath(Application.ExeName) + 'tabs.ini' - else - TabsIniFilename := AppSettings.DirnameUserAppData + 'tabs.ini'; + TabsIniFilename := AppSettings.DirnameUserAppData + 'tabs.ini'; WaitingSince := GetTickCount64; Attempts := 0; while not FileIsWritable(TabsIniFilename) do begin @@ -2360,7 +2396,7 @@ function TMainForm.InitTabsIniFile: TIniFile; end; // Catch errors when file cannot be created if not FileExists(TabsIniFilename) then begin - SaveUnicodeFile(TabsIniFilename, ''); + SaveUnicodeFile(TabsIniFilename, '', UTF8NoBOMEncoding); end; Result := TIniFile.Create(TabsIniFilename); end; @@ -2407,6 +2443,8 @@ procedure TMainForm.StoreTabs; TabsIni.WriteInteger(Section, TQueryTab.IdentEditorTopLine, Tab.Memo.TopLine); if TabsIni.ReadBool(Section, TQueryTab.IdentTabFocused, False) <> (Tab.TabSheet = Tab.TabSheet.PageControl.ActivePage) then TabsIni.WriteBool(Section, TQueryTab.IdentTabFocused, (Tab.TabSheet = Tab.TabSheet.PageControl.ActivePage)); + if TabsIni.ReadString(Section, TQueryTab.IdentFileEncoding, 'UTF-8') <> Tab.FileEncoding then + TabsIni.WriteString(Section, TQueryTab.IdentFileEncoding, Tab.FileEncoding); end; // Tabs with deleted backup files don't get restored anyway. But a section from a closed user loaded tab @@ -2455,6 +2493,7 @@ function TMainForm.RestoreTabs: Boolean; BindParams: String; TabFocused: Boolean; TabLoadStart, TabLoadTime: UInt64; + Encoding: TEncoding; const SlowLoadMilliseconds = 5000; @@ -2489,6 +2528,7 @@ function TMainForm.RestoreTabs: Boolean; BindParams := TabsIni.ReadString(Section, TQueryTab.IdentBindParams, ''); EditorTopLine := TabsIni.ReadInteger(Section, TQueryTab.IdentEditorTopLine, 1); TabFocused := TabsIni.ReadBool(Section, TQueryTab.IdentTabFocused, False); + Encoding := GetEncodingByName(TabsIni.ReadString(Section, TQueryTab.IdentFileEncoding, 'UTF-8')); // Don't restore this tab if it belongs to a different running Heidi process if (pid > 0) and (pid <> GetCurrentProcessId) and ProcessExists(pid, APPNAME) then begin @@ -2502,15 +2542,16 @@ function TMainForm.RestoreTabs: Boolean; if FileExists(BackupFilename) then begin Tab := GetOrCreateEmptyQueryTab(False); Tab.Uid := Section; - Tab.LoadContents(BackupFilename, True, UTF8NoBOMEncoding); + Tab.LoadContents(BackupFilename, True, Encoding); Tab.MemoFilename := Filename; Tab.Memo.Modified := True; if not TabCaption.IsEmpty then SetTabCaption(Tab.TabSheet.PageIndex, TabCaption); if EditorHeight > 50 then Tab.pnlMemo.Height := EditorHeight; - if HelpersWidth > 50 then - Tab.pnlHelpers.Width := HelpersWidth; + // Causes sporadic long-waiters: + //if HelpersWidth > 50 then + // Tab.pnlHelpers.Width := HelpersWidth; Tab.ListBindParams.AsText := BindParams; Tab.BindParamsActivated := Tab.ListBindParams.Count > 0; Tab.Memo.TopLine := EditorTopLine; @@ -2525,14 +2566,15 @@ function TMainForm.RestoreTabs: Boolean; if FileExists(Filename) then begin Tab := GetOrCreateEmptyQueryTab(False); Tab.Uid := Section; - Tab.LoadContents(Filename, True, UTF8NoBOMEncoding); + Tab.LoadContents(Filename, True, Encoding); Tab.MemoFilename := Filename; if not TabCaption.IsEmpty then SetTabCaption(Tab.TabSheet.PageIndex, TabCaption); if EditorHeight > 50 then Tab.pnlMemo.Height := EditorHeight; - if HelpersWidth > 50 then - Tab.pnlHelpers.Width := HelpersWidth; + // Causes sporadic long-waiters: + //if HelpersWidth > 50 then + // Tab.pnlHelpers.Width := HelpersWidth; Tab.ListBindParams.AsText := BindParams; Tab.BindParamsActivated := Tab.ListBindParams.Count > 0; Tab.Memo.TopLine := EditorTopLine; @@ -2573,6 +2615,20 @@ function TMainForm.RestoreTabs: Boolean; end; +procedure TMainForm.SetHintFontByControl(Control: TWinControl=nil); +var + UseFontName: String; +begin + // Set hint font name to match the underlying control + if Assigned(Control) and (Control is TSynMemo) then + UseFontName := TSynMemo(Control).Font.Name + else + UseFontName := FDefaultHintFontName; + if Screen.HintFont.Name <> UseFontName then + Screen.HintFont.Name := UseFontName; +end; + + procedure TMainForm.TimerStoreTabsTimer(Sender: TObject); begin // Backup unsaved content every 10 seconds @@ -2738,7 +2794,7 @@ procedure TMainForm.FormResize(Sender: TObject); var MaxPixels: Integer; begin - MaxPixels := StatusBar.Canvas.TextWidth(SampleText) + VirtualImageListMain.Width + 20; + MaxPixels := StatusBar.Canvas.TextWidth(SampleText) + VirtualImageListMain.Width + 30; Result := Round(Min(MaxPixels, Width / 100 * MaxPercentage)); end; begin @@ -2750,11 +2806,11 @@ procedure TMainForm.FormResize(Sender: TObject); Exit; // Super intelligent calculation of status bar panel width - w1 := CalcPanelWidth('r10 : c10 (10 KiB)', 10); - w2 := CalcPanelWidth('Connected: 1 day, 00:00 h', 10); - w3 := CalcPanelWidth('MariaDB or MySQL 5.7.6', 15); - w4 := CalcPanelWidth('Uptime: 13 days, 00:00 h', 15); - w5 := CalcPanelWidth('Server time: 20:00 ', 10); + w1 := CalcPanelWidth('r10 : c10 (10 KiB)', 12); + w2 := CalcPanelWidth('Connected: 1 day, 00:00 h', 12); + w3 := CalcPanelWidth('MariaDB or MySQL 5.7.6', 12); + w4 := CalcPanelWidth('Uptime: 13 days, 00:00 h', 12); + w5 := CalcPanelWidth('Server time: 20:00 PM', 12); w6 := CalcPanelWidth('DummyDummyDummyDummyDummy', 20); w0 := StatusBar.Width - w1 - w2 - w3 - w4 - w5 - w6; //logsql(format('IconWidth:%d 0:%d 1:%d 2:%d 3:%d 4:%d 5:%d 6:%d', [VirtualImageListMain.Width, w0, w1, w2, w3, w4, w5, w6])); @@ -2782,9 +2838,11 @@ procedure TMainForm.FormResize(Sender: TObject); FixQueryTabCloseButtons; // Right aligned button + // Do not set ToolBar.Align to alRight. See issue #1967 if ToolBarDonate.Visible then begin - ToolBarDonate.Width := ToolBarDonate.Buttons[0].Width; + //ToolBarDonate.Width := ToolBarDonate.Buttons[0].Width; ToolBarDonate.Left := ControlBarMain.Width - ToolBarDonate.Width; + //ToolBarDonate.Buttons[0].Height := ToolBarMainButtons.Buttons[0].Height; end; end; @@ -2901,6 +2959,10 @@ procedure TMainForm.menuClearDataTabFilterClick(Sender: TObject); AppSettings.DeleteValue(asFilter); LogSQL(f_('Data filter for %s deleted', [ActiveDbObj.Name]), lcInfo); end; + if AppSettings.ValueExists(asSort) then begin + AppSettings.DeleteValue(asSort); + LogSQL(f_('Sort order for %s deleted', [ActiveDbObj.Name]), lcInfo); + end; end; @@ -2929,7 +2991,9 @@ procedure TMainForm.actTableToolsExecute(Sender: TObject); else if Sender = actExportTables then FTableToolsDialog.ToolMode := tmSQLExport else if Sender = actBulkTableEdit then - FTableToolsDialog.ToolMode := tmBulkTableEdit; + FTableToolsDialog.ToolMode := tmBulkTableEdit + else if Sender = actGenerateData then + FTableToolsDialog.ToolMode := tmGenerateData; FTableToolsDialog.ShowModal; FreeAndNil(FTableToolsDialog); end; @@ -3044,7 +3108,7 @@ procedure TMainForm.menuConnectionsPopup(Sender: TObject); AppSettings.GetSessionPaths('', SessionPaths); for i:=0 to SessionPaths.Count-1 do begin item := TMenuItem.Create(menuConnections); - item.Caption := SessionPaths[i]; + item.Caption := EscapeHotkeyPrefix(SessionPaths[i]); item.OnClick := SessionConnect; for Connection in Connections do begin if SessionPaths[i] = Connection.Parameters.SessionPath then begin @@ -3071,7 +3135,7 @@ procedure TMainForm.MainMenuFileClick(Sender: TObject); AppSettings.GetSessionPaths('', SessionPaths); for i:=0 to SessionPaths.Count-1 do begin Item := TMenuItem.Create(menuConnectTo); - Item.Caption := SessionPaths[i]; + Item.Caption := EscapeHotkeyPrefix(SessionPaths[i]); Item.OnClick := SessionConnect; for Connection in Connections do begin if SessionPaths[i] = Connection.Parameters.SessionPath then begin @@ -3164,7 +3228,7 @@ function TMainForm.GetCurrentQuery(Tab: TQueryTab): String; begin // Return SQL query on cursor position Result := ''; - BatchAll := TSQLBatch.Create; + BatchAll := TSQLBatch.Create(ActiveConnection.Parameters.NetTypeGroup); BatchAll.SQL := Tab.Memo.Text; PrevQuery := nil; for Query in BatchAll do begin @@ -3187,6 +3251,7 @@ function TMainForm.GetCurrentQuery(Tab: TQueryTab): String; procedure TMainForm.actExecuteQueryExecute(Sender: TObject); var ProfileNode: PVirtualNode; + Conn: TDBConnection; Batch: TSQLBatch; Tab: TQueryTab; BindParam: Integer; @@ -3196,11 +3261,12 @@ procedure TMainForm.actExecuteQueryExecute(Sender: TObject); ContainsUnsafeQueries, DoExecute: Boolean; begin Tab := QueryTabs.ActiveTab; + Conn := ActiveConnection; OperationRunning(True); DoExecute := True; ShowStatusMsg(_('Splitting SQL queries ...')); - Batch := TSQLBatch.Create; + Batch := TSQLBatch.Create(Conn.Parameters.NetTypeGroup); if Sender = actExecuteSelection then begin Batch.SQL := Tab.Memo.SelText; Tab.LeftOffsetInMemo := Tab.Memo.SelStart; @@ -3212,7 +3278,7 @@ procedure TMainForm.actExecuteQueryExecute(Sender: TObject); ErrorDialog(_('Current query is empty'), _('Please move the cursor inside the query you want to use.')); DoExecute := False; end else begin - Batch.SQL := 'EXPLAIN ' + CurrentQuery; + Batch.SQL := Conn.SqlProvider.GetSql(qExplain, [CurrentQuery]); end; end else begin Batch.SQL := Tab.Memo.Text; @@ -3268,7 +3334,7 @@ procedure TMainForm.actExecuteQueryExecute(Sender: TObject); ProfileNode := FindNode(Tab.treeHelpers, TQueryTab.HelperNodeProfile, nil); Tab.DoProfile := Assigned(ProfileNode) and (Tab.treeHelpers.CheckState[ProfileNode] in CheckedStates); if Tab.DoProfile then try - ActiveConnection.Query('SET profiling=1'); + Conn.Query('SET profiling=1'); except on E:EDbError do begin ErrorDialog(f_('Query profiling requires %s or later, and the server must not be configured with %s.', ['MySQL 5.0.37', '--disable-profiling']), E.Message); @@ -3278,9 +3344,9 @@ procedure TMainForm.actExecuteQueryExecute(Sender: TObject); // Start the execution thread Screen.Cursor := crAppStart; - ActiveConnection.Ping(True); // Prevents SynEdit paint exceptions if connection was killed outside + Conn.Ping(True); // Prevents SynEdit paint exceptions if connection was killed outside Tab.QueryRunning := True; - Tab.ExecutionThread := TQueryThread.Create(ActiveConnection, Batch, Tab.Number); + Tab.ExecutionThread := TQueryThread.Create(Conn, Batch, Tab.Number); end; ValidateQueryControls(Sender); @@ -3300,7 +3366,8 @@ procedure TMainForm.AfterQueryExecution(Thread: TQueryThread); Tab: TQueryTab; NewTab: TResultTab; col: TVirtualTreeColumn; - TabCaption: String; + TabCaption, TabCaptions, BatchHead: String; + TabCaptionsList: TStringList; TabsetColor: TColor; Results: TDBQuery; i, HeaderPadding, HeaderLineBreaks: Integer; @@ -3321,26 +3388,31 @@ procedure TMainForm.AfterQueryExecution(Thread: TQueryThread); Tab.tabsetQuery.UnselectedColor := clBtnFace; end; + // Get tab caption list from comment, similar to a name:xyz in a single query + BatchHead := Copy(Thread.Batch.SQL, 1, SIZE_KB); + TabCaptions := RegExprGetMatch('--\s+names\:\s*([^\r\n]+)', BatchHead, 1, False, True); + TabCaptionsList := Explode(',', TabCaptions); + LogSQL('TabCaptionsList: '+TabCaptionsList.CommaText, lcDebug); // Create result tabs for Results in Thread.Connection.GetLastResults do begin NewTab := TResultTab.Create(Tab); Tab.ResultTabs.Add(NewTab); NewTab.Results := Results; try - TabCaption := NewTab.Results.TableName; - // Add postfix to tab name so tab captions are unique - i := 1; - while Tab.tabsetQuery.Tabs.IndexOf(TabCaption) > -1 do begin - Inc(i); - TabCaption := NewTab.Results.TableName + ' #' + IntToStr(i); + TabCaption := NewTab.Results.ResultName; + if TabCaption.IsEmpty and (TabCaptionsList.Count > NewTab.TabIndex) then + TabCaption := TabCaptionsList[NewTab.TabIndex]; + if TabCaption.IsEmpty then + TabCaption := NewTab.Results.TableName; + except + on E:EDbError do begin + TabCaption := _('Result')+' #'+IntToStr(NewTab.TabIndex+1); end; - except on E:EDbError do - TabCaption := _('Result')+' #'+IntToStr(Tab.ResultTabs.Count); end; - + TabCaption := Trim(TabCaption); TabCaption := TabCaption + ' (' + FormatNumber(Results.RecordCount) + 'r × ' + FormatNumber(Results.ColumnCount) + 'c)'; Tab.tabsetQuery.Tabs.Add(TabCaption); - NewTab.Grid.Name := Format('Tab%dGrid%d', [Tab.Number, Tab.ResultTabs.Count]); + NewTab.Grid.Name := Format('Tab%dGrid%d', [Tab.Number, NewTab.TabIndex+1]); NewTab.Grid.BeginUpdate; NewTab.Grid.Header.Options := NewTab.Grid.Header.Options + [hoVisible]; @@ -3358,6 +3430,7 @@ procedure TMainForm.AfterQueryExecution(Thread: TQueryThread); for i:=0 to NewTab.Results.ColumnCount-1 do begin col := NewTab.Grid.Header.Columns.Add; col.Text := NewTab.Results.ColumnNames[i]; + col.Hint := _('Source table') + ': ' + IfEmpty(NewTab.Results.TableName(i), '-'); if NewTab.Results.DataType(i).Category in [dtcInteger, dtcReal] then col.Alignment := taRightJustify; if NewTab.Results.ColIsPrimaryKeyPart(i) then @@ -3377,20 +3450,20 @@ procedure TMainForm.AfterQueryExecution(Thread: TQueryThread); if Tab.tabsetQuery.TabIndex = -1 then Tab.tabsetQuery.TabIndex := 0; end; + Thread.Connection.ShowWarnings; ShowStatusMsg; end; procedure TMainForm.FinishedQueryExecution(Thread: TQueryThread); var - Tab, WarningsTab: TQueryTab; - MetaInfo, ErroneousSQL, RegName, MsgTitle, MsgText: String; + Tab: TQueryTab; + MetaInfo, ErroneousSQL, RegName: String; ProfileAllTime: Extended; ProfileNode: PVirtualNode; History: TQueryHistory; HistoryItem: TQueryHistoryItem; - Warnings: TDBQuery; - HistoryNum, MaxWarnings, RegItemsSize, KeepDays: Integer; + HistoryNum, RegItemsSize, KeepDays: Integer; DoDelete, ValueFound: Boolean; MinDate: TDateTime; @@ -3477,37 +3550,6 @@ procedure TMainForm.FinishedQueryExecution(Thread: TQueryThread); Thread.Connection.Query('SET profiling=0'); end; - // Show warnings - if Thread.WarningCount > 0 then begin - MsgTitle := f_('Your query produced %s warnings.', [FormatNumber(Thread.WarningCount)]); - MsgText := ''; - Warnings := Thread.Connection.GetResults('SHOW WARNINGS LIMIT 5'); - if Warnings.RecordCount < 5 then - MsgText := MsgText + _('Warnings from last query:')+CRLF - else if Warnings.RecordCount < Thread.WarningCount then - MsgText := MsgText + f_('First %s warnings:', [FormatNumber(Warnings.RecordCount)])+CRLF; - while not Warnings.Eof do begin - MsgText := MsgText + Warnings.Col('Level') + ': ' + Warnings.Col('Message') + CRLF; - Warnings.Next; - end; - MsgText := Trim(MsgText); - if (Warnings.RecordCount = Thread.WarningCount) or (Warnings.RecordCount < 5) then - MessageDialog(MsgTitle, MsgText, mtWarning, [mbOk], asQueryWarningsMessage) - else begin - MsgText := MsgText + CRLF+CRLF + _('Show all warnings in a new query tab?'); - MaxWarnings := MakeInt(Thread.Connection.GetVar('SELECT @@max_error_count')); - if MaxWarnings < Thread.WarningCount then - MsgText := MsgText + CRLF+CRLF+ f_('The server variable %s is currently set to %d, so you won''t see all warnings.', ['@@max_error_count', MaxWarnings]); - if MessageDialog(MsgTitle, MsgText, mtWarning, [mbYes, mbNo], asQueryWarningsMessage) = mrYes then begin - actNewQueryTab.Execute; - WarningsTab := QueryTabs[QueryTabs.Count-1]; - WarningsTab.Memo.Text := 'SHOW WARNINGS'; - actExecuteQueryExecute(WarningsTab); - end; - end; - end; - - // Store successful query packet in history if it's not a batch. // Assume that a bunch of up to 5 queries is not a batch. AppSettings.ResetPath; @@ -3639,7 +3681,7 @@ procedure TMainForm.actDataPreviewUpdate(Sender: TObject); // Enable or disable ImageView action Grid := ActiveGrid; (Sender as TAction).Enabled := (Grid <> nil) - and (Grid.FocusedColumn <> NoColumn) + and (Grid.FocusedColumn > 0) // may be NoColumn/-1 or InvalidColumn/-2 and (GridResult(Grid).DataType(Grid.FocusedColumn-1).Category = dtcBinary) end; @@ -3819,13 +3861,12 @@ procedure TMainForm.actInsertFilesExecute(Sender: TObject); procedure TMainForm.actDropObjectsExecute(Sender: TObject); var msg, db: String; - Node: PVirtualNode; + Node, SiblingDB: PVirtualNode; Obj: PDBObject; DBObject: TDBObject; ObjectList: TDBObjectList; Editor: TDBObjectEditor; Conn: TDBConnection; - DisableForeignKeys: Boolean; begin Conn := ActiveConnection; @@ -3841,8 +3882,16 @@ procedure TMainForm.actDropObjectsExecute(Sender: TObject); try db := DBObject.Database; Node := FindDBNode(DBtree, Conn, db); - SetActiveDatabase('', Conn); - Conn.Query(Conn.GetSQLSpecifity(spDatabaseDrop, [Conn.QuoteIdent(db)])); + // Set focus on previous or next database, to prevent "Cannot drop database xyz, because it is currently in use" + // MS SQL on top cannot "un-use" the current database + SiblingDB := DBtree.GetNextSibling(Node); + if not Assigned(SiblingDB) then + SiblingDB := DBtree.GetPreviousSibling(Node); + if Assigned(SiblingDB) then + SetActiveDatabase(DBtree.Text[SiblingDB, 0], Conn) + else + SetActiveDatabase('', Conn); // Fallback if there is no sibling. Works on MySQL only. + Conn.Query(qDatabaseDrop, [Conn.QuoteIdent(db)]); DBtree.DeleteNode(Node); Conn.ClearDbObjects(db); Conn.RefreshAllDatabases; @@ -3881,9 +3930,8 @@ procedure TMainForm.actDropObjectsExecute(Sender: TObject); if MessageDialog(f_('Drop %d object(s) in database "%s"?', [ObjectList.Count, Conn.Database]), msg, mtCriticalConfirmation, [mbok,mbcancel]) = mrOk then begin try // Disable foreign key checks to avoid SQL errors - DisableForeignKeys := (Conn.Parameters.NetTypeGroup = ngMySQL) and (Conn.ServerVersionInt >= 40014); - if DisableForeignKeys then - Conn.Query('SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0'); + if Conn.SqlProvider.Has(qDisableForeignKeyChecks) then + Conn.Query(qDisableForeignKeyChecks); // Compose and run DROP [TABLE|VIEW|...] queries Editor := ActiveObjectEditor; for DBObject in ObjectList do begin @@ -3891,8 +3939,8 @@ procedure TMainForm.actDropObjectsExecute(Sender: TObject); if Assigned(Editor) and Editor.Modified and Editor.DBObject.IsSameAs(DBObject) then Editor.Modified := False; end; - if DisableForeignKeys then - Conn.Query('SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS'); + if Conn.SqlProvider.Has(qEnableForeignKeyChecks) then + Conn.Query(qEnableForeignKeyChecks); // Refresh ListTables + dbtree so the dropped tables are gone: Conn.ClearDbObjects(ActiveDatabase); RefreshTree; @@ -3951,10 +3999,9 @@ procedure TMainForm.actLaunchCommandlineExecute(Sender: TObject); // Load SQL-file, make sure that SheetQuery is activated procedure TMainForm.actLoadSQLExecute(Sender: TObject); var - i, ProceedResult: Integer; + ProceedResult: Integer; Dialog: TExtFileOpenDialog; Encoding: TEncoding; - Tab: TQueryTab; begin AppSettings.ResetPath; Dialog := TExtFileOpenDialog.Create(Self); @@ -3976,14 +4023,7 @@ procedure TMainForm.actLoadSQLExecute(Sender: TObject); end; if ProceedResult = mrYes then begin - if not RunQueryFiles(Dialog.Files, Encoding, Sender=actRunSQL) then begin - for i:=0 to Dialog.Files.Count-1 do begin - Tab := GetOrCreateEmptyQueryTab(False); - Tab.LoadContents(Dialog.Files[i], True, Encoding); - if i = Dialog.Files.Count-1 then - SetMainTab(Tab.TabSheet); - end; - end; + OpenQueryFiles(Dialog.Files, Encoding, Sender=actRunSQL); end; AppSettings.WriteInt(asFileDialogEncoding, Dialog.EncodingIndex, Self.Name); end; @@ -3991,6 +4031,51 @@ procedure TMainForm.actLoadSQLExecute(Sender: TObject); end; +procedure TMainForm.OpenQueryFiles(Filenames: TStrings; Encoding: TEncoding; ForceRun: Boolean); +var + Tab, FileInTab: TQueryTab; + FileHints: TStringList; + i: Integer; +begin + // Decides whether to run or load files, prevents duplicates etc. + if RunQueryFiles(Filenames, Encoding, ForceRun) then + Exit; + + FileHints := TStringList.Create; + + for i:=0 to Filenames.Count-1 do begin + + FileInTab := nil; + for Tab in QueryTabs do begin + if Tab.MemoFilename = Filenames[i] then begin + FileInTab := Tab; + FileHints.Add(f_('This file is already open in query tab #%d.', [FileInTab.Number]) + ' ' + ExtractFileName(Filenames[i])); + if i = Filenames.Count-1 then + SetMainTab(FileInTab.TabSheet); + Break; + end; + end; + + if not Assigned(FileInTab) then begin + Tab := GetOrCreateEmptyQueryTab(False); + Tab.LoadContents(Filenames[i], True, Encoding); + if i = Filenames.Count-1 then + SetMainTab(Tab.TabSheet); + end; + end; + + if not FileHints.IsEmpty then begin + if MainFormAfterCreateDone then + MessageDialog(FileHints.Text, mtInformation, [mbOK]) + else begin + for i:=0 to FileHints.Count-1 do + LogSQL(FileHints[i]); + end; + end; + FileHints.Free; +end; + + function TMainForm.RunQueryFiles(Filenames: TStrings; Encoding: TEncoding; ForceRun: Boolean): Boolean; var i, FilesProcessed: Integer; @@ -4153,7 +4238,7 @@ function TMainForm.RunQueryFile(FileName: String; Encoding: TEncoding; Conn: TDB ErrorCount := 0; RowsAffected := 0; LinesRemain := ''; - Queries := TSQLBatch.Create; + Queries := TSQLBatch.Create(Conn.Parameters.NetTypeGroup); try // Start file operations @@ -4205,6 +4290,7 @@ function TMainForm.RunQueryFile(FileName: String; Encoding: TEncoding; Conn: TDB try Conn.Query(Queries[i].SQL, False, lcScript); RowsAffected := RowsAffected + Conn.RowsAffected; + Conn.ShowWarnings; except on E:Exception do begin if actQueryStopOnErrors.Checked then @@ -4270,7 +4356,7 @@ procedure TMainForm.SessionConnect(Sender: TObject); for i:=High(FTreeClickHistory) downto Low(FTreeClickHistory) do begin if FTreeClickHistory[i] <> nil then begin DBObj := DBtree.GetNodeData(FTreeClickHistory[i]); - if DBObj = nil then // Session disconnected + if (DBObj = nil) or (DBObj.Connection = nil) or (not DBObj.Connection.Active) then // Session disconnected Break; if DBObj.Connection.Parameters.SessionPath = SessionPath then begin Node := FTreeClickHistory[i]; @@ -4306,9 +4392,7 @@ procedure TMainForm.SessionConnect(Sender: TObject); function TMainform.InitConnection(Params: TConnectionParameters; ActivateMe: Boolean; var Connection: TDBConnection): Boolean; var RestoreLastActiveDatabase: Boolean; - StartupScript, LastActiveDatabase: String; - StartupBatch: TSQLBatch; - Query: TSQLSentence; + LastActiveDatabase: String; SessionNode, DBNode: PVirtualNode; begin Connection := Params.CreateConnection(Self); @@ -4351,24 +4435,6 @@ function TMainform.InitConnection(Params: TConnectionParameters; ActivateMe: Boo end; end; - // Process startup script - StartupScript := Trim(Connection.Parameters.StartupScriptFilename); - if StartupScript <> '' then begin - StartupScript := ExpandFileName(StartupScript); - if not FileExists(StartupScript) then - ErrorDialog(f_('Startup script file not found: %s', [StartupScript])) - else begin - StartupBatch := TSQLBatch.Create; - StartupBatch.SQL := ReadTextfile(StartupScript, nil); - for Query in StartupBatch do try - Connection.Query(Query.SQL); - except - // Suppress popup, errors get logged into SQL log - end; - StartupBatch.Free; - end; - end; - if Params.WantSSL and not Connection.IsSSL then begin MessageDialog(_('SSL not used.'), _('Your SSL settings were not accepted by the server, or the server does not support any SSL configuration.'), @@ -4381,6 +4447,7 @@ function TMainform.InitConnection(Params: TConnectionParameters; ActivateMe: Boo // Apply favorite object paths AppSettings.SessionPath := Params.SessionPath; Connection.Favorites.Text := AppSettings.ReadString(asFavoriteObjects); + actFavoriteObjectsOnly.Checked := False; // Tree node filtering needs a hit once when connected editDatabaseTableFilterChange(Self); @@ -4521,8 +4588,8 @@ procedure TMainForm.actEmptyTablesExecute(Sender: TObject); ErrorDialog(_('No table(s) selected.')) else begin Conn := ActiveConnection; - QueryDisableChecks := Conn.GetSQLSpecifity(spDisableForeignKeyChecks); - QueryEnableChecks := Conn.GetSQLSpecifity(spEnableForeignKeyChecks); + QueryDisableChecks := Conn.SqlProvider.GetSql(qDisableForeignKeyChecks); + QueryEnableChecks := Conn.SqlProvider.GetSql(qEnableForeignKeyChecks); if (Win32MajorVersion >= 6) and StyleServices.Enabled then begin Dialog := TTaskDialog.Create(Self); Dialog.Text := f_('Empty %d table(s) and/or view(s)?', [Objects.count]); @@ -4547,7 +4614,7 @@ procedure TMainForm.actEmptyTablesExecute(Sender: TObject); Conn.Query(QueryDisableChecks); try for TableOrView in Objects do begin - Conn.Query(Conn.GetSQLSpecifity(spEmptyTable) + TableOrView.QuotedName); + Conn.Query(qEmptyTable, [TableOrView.QuotedName]); ProgressStep; end; actRefresh.Execute; @@ -4681,7 +4748,7 @@ procedure TMainForm.actRunRoutinesExecute(Sender: TObject); procedure TMainForm.actNewWindowExecute(Sender: TObject); begin - ShellExec( ExtractFileName(paramstr(0)), ExtractFilePath(paramstr(0)) ); + ShellExec( ExtractFileName(paramstr(0)), GetAppDir); end; @@ -4755,9 +4822,25 @@ procedure TMainForm.actRefreshExecute(Sender: TObject); tab1, tab2: TTabSheet; List: TVirtualStringTree; OldDbObject: TDBObject; + DoProceed: Boolean; + i: Integer; +const + HeaderDragStates: THeaderStates = [ + hsAutoSizing, hsDragging, hsDragPending, hsColumnWidthTracking, hsColumnWidthTrackPending, hsHeightTracking, hsHeightTrackPending, hsResizing + ]; begin // Refresh - // Force data tab update when appropriate. + + // Do not refresh when *any* tree is dragged or resized by user in this moment. + // This is not limited to the focused control, as we also refresh ListDatabases if only its tab is active. + for i:=0 to ComponentCount-1 do begin + if not(Components[i] is TVirtualStringTree) then + continue; + if (HeaderDragStates * TVirtualStringTree(Components[i]).Header.States <> []) then begin + Exit; + end; + end; + // Disable refresh action and re-enable in ApplicationOnIdle event tab1 := PageControlMain.ActivePage; actRefresh.Enabled := False; @@ -4782,7 +4865,11 @@ procedure TMainForm.actRefreshExecute(Sender: TObject); OldDbObject.Assign(FActiveDbObj); RefreshTree(OldDbObject); end else if tab1 = tabEditor then begin - RefreshTree; + DoProceed := True; + if ActiveObjectEditor.Modified then + DoProceed := MessageDialog(_('Discard changes?'), mtConfirmation, [mbCancel, mbOK]) = mrOk; + if DoProceed then + RefreshTree; end else if tab1 = tabData then InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); end; @@ -4829,16 +4916,6 @@ procedure TMainForm.actSQLhelpExecute(Sender: TObject); end; -procedure TMainForm.actSynchronizeDatabaseExecute(Sender: TObject); -var - SyncForm: TfrmSyncDB; -begin - SyncForm := TfrmSyncDB.Create(Self); - SyncForm.ShowModal; - SyncForm.Free; -end; - - procedure TMainForm.actSynEditCompletionProposeExecute(Sender: TObject); begin // Show completion proposal explicitely, without the use of its own ShortCut property, @@ -4894,10 +4971,7 @@ procedure TMainForm.actSynMoveUpExecute(Sender: TObject); } procedure TMainform.CallSQLHelpWithKeyword( keyword: String ); begin - if FActiveDbObj.Connection.Parameters.IsAnyMySQL - and (FActiveDbObj.Connection.ServerVersionInt >= 40100) - and (not FActiveDbObj.Connection.Parameters.IsProxySQLAdmin) - then begin + if FActiveDbObj.Connection.SqlProvider.Has(qHelpKeyword) then begin if not Assigned(SqlHelpDialog) then SqlHelpDialog := TfrmSQLhelp.Create(Self); SqlHelpDialog.Show; @@ -4933,7 +5007,8 @@ procedure TMainForm.actSaveSynMemoToTextfileExecute(Sender: TObject); Screen.Cursor := crHourGlass; SaveUnicodeFile( Dialog.FileName, - Implode(GetLineBreak(Dialog.LineBreakIndex), Memo.Lines) + Implode(GetLineBreak(Dialog.LineBreakIndex), Memo.Lines), + UTF8NoBOMEncoding ); Screen.Cursor := crDefault; end; @@ -5044,6 +5119,29 @@ procedure TMainForm.actQueryStopOnErrorsExecute(Sender: TObject); end; +procedure TMainForm.actQueryTableExecute(Sender: TObject); +var + Objects: TDBObjectList; + Obj: TDBObject; + Tab: TQueryTab; + Conn: TDBConnection; +begin + // Query table data + Conn := ActiveConnection; + if not Assigned(Conn) then + Exit; + Objects := GetFocusedObjects(Sender, [lntTable, lntView]); + + if Objects.Count = 0 then + ErrorDialog(_('No table selected.'), _('Please select one or more table(s) or view(s).')); + + for Obj in Objects do begin + Tab := GetOrCreateEmptyQueryTab(True); + Tab.Memo.Text := Conn.ApplyLimitClause('SELECT', '* FROM '+Obj.QuotedName, AppSettings.ReadInt(asDatagridRowsPerStep), 0); + actExecuteQueryExecute(Sender); + end; +end; + procedure TMainForm.actQueryWordWrapExecute(Sender: TObject); begin // SetupSynEditors applies all customizations to any SynEditor @@ -5203,23 +5301,19 @@ procedure TMainform.popupQueryLoadClick(Sender: TObject); Filename: String; FileList: TStringList; p: Integer; - Tab: TQueryTab; begin // Click on the popupQueryLoad Filename := (Sender as TMenuItem).Caption; Filename := StripHotkey(Filename); if Pos('\', Filename) = 0 then // assuming we load a snippet - Filename := AppSettings.DirnameSnippets + Filename + '.sql' + Filename := AppSettings.DirnameSnippets + Filename + FILEEXT_SNIPPET else begin // assuming we load a file from the recent-list p := Pos(' ', Filename) + 1; filename := Copy(Filename, p, Length(Filename)); end; FileList := TStringList.Create; FileList.Add(Filename); - if not RunQueryFiles(FileList, nil, false) then begin - Tab := GetOrCreateEmptyQueryTab(True); - Tab.LoadContents(Filename, True, nil); - end; + OpenQueryFiles(FileList, nil, False); FileList.Free; end; @@ -5298,7 +5392,7 @@ procedure TMainForm.SetDelimiter(Value: String); Msg := _('Empty value.') else begin rx := TRegExpr.Create; - rx.Expression := '(/\*|--|#|\''|\"|`)'; + rx.Expression := '(/\*|--|#|\''|\")'; if rx.Exec(Value) then Msg := _('Start-of-comment tokens or string literal markers are not allowed.') end; @@ -5545,9 +5639,7 @@ procedure TMainForm.LogSQL(Msg: String; Category: TDBLogCategory=lcInfo; Connect snip := (MaxLineWidth > 0) and (Len > MaxLineWidth); IsSQL := LogItem.Category in [lcSQL, lcUserFiredSQL]; if snip then begin - Msg := - Copy(Msg, 0, MaxLineWidth) + - '/* '+f_('large SQL query (%s), snipped at %s characters', [FormatByteNumber(Len), FormatNumber(MaxLineWidth)]) + ' */'; + Msg := Copy(Msg, 1, MaxLineWidth) + '...'; end else if (not snip) and IsSQL then Msg := Msg + Delimiter; if not IsSQL then @@ -5632,7 +5724,7 @@ procedure TMainForm.actAttachDatabaseExecute(Sender: TObject); try for i:=0 to NewFiles.Count-1 do begin // Remove path if it's the application directory - if ExtractFilePath(NewFiles[i]) = ExtractFilePath(Application.ExeName) then + if ExtractFilePath(NewFiles[i]) = GetAppDir then NewFiles[i] := ExtractFileName(NewFiles[i]); if OldFiles.IndexOf(NewFiles[i]) = -1 then begin OldFiles.Add(NewFiles[i]); @@ -5748,12 +5840,13 @@ procedure TMainForm.AnyGridAdvancedHeaderDraw(Sender: TVTHeader; var PaintInfo: THeaderPaintInfo; const Elements: THeaderPaintElements); var PaintArea, TextArea, IconArea, SortArea: TRect; - SortText, ColCaption: String; + SortText, ColCaption, ColIndex: String; TextSpace, ColSortIndex, NumCharTop: Integer; ColSortDirection: VirtualTrees.TSortDirection; - Size: TSize; - DC: HDC; + TextSize: TSize; + DeviceContext: HDC; DrawFormat: Cardinal; + ColInfo: TTableColumn; const NumSortChars: Array of Char = ['¹','²','³','⁴','⁵','⁶','⁷','⁸','⁹','⁺']; @@ -5787,13 +5880,33 @@ procedure TMainForm.AnyGridAdvancedHeaderDraw(Sender: TVTHeader; PaintArea := PaintInfo.PaintRectangle; PaintArea.Inflate(-PaintInfo.Column.Margin, 0); - DC := PaintInfo.TargetCanvas.Handle; + DeviceContext := PaintInfo.TargetCanvas.Handle; // Draw column name. Code taken from TVirtualTreeColumns.DrawButtonText and modified for our needs if hpeText in Elements then begin + + TextArea := PaintArea; + SetBkMode(DeviceContext, TRANSPARENT); + DrawFormat := DT_TOP or DT_NOPREFIX or DT_LEFT; + + if AppSettings.ReadBool(asShowRowId) and (PaintInfo.Column.Index > 0) then begin + // Paint gray column number left to its caption + ColIndex := PaintInfo.Column.Index.ToString; + if Sender.Treeview = DataGrid then begin + ColInfo := SelectedTableColumns.FindByName(PaintInfo.Column.Text); + if Assigned(ColInfo) then + ColIndex := (SelectedTableColumns.IndexOf(ColInfo) + 1).ToString; + end; + + SetTextColor(DeviceContext, ColorToRGB(clGrayText)); + DrawTextW(DeviceContext, PWideChar(ColIndex), Length(ColIndex), PaintArea, DrawFormat); + // Move caption text to right + GetTextExtentPoint32W(DeviceContext, PWideChar(ColIndex), Length(ColIndex), TextSize); + Inc(TextArea.Left, TextSize.cx + 5); + end; + ColCaption := PaintInfo.Column.Text; // Leave space for icons - TextArea := PaintArea; if PaintInfo.Column.ImageIndex > -1 then Dec(TextArea.Right, Sender.Images.Width); GetSortIndex(PaintInfo.Column, ColSortIndex, ColSortDirection); @@ -5802,16 +5915,14 @@ procedure TMainForm.AnyGridAdvancedHeaderDraw(Sender: TVTHeader; if not (coWrapCaption in PaintInfo.Column.Options) then begin // Do we need to shorten the caption due to limited space? - GetTextExtentPoint32W(DC, PWideChar(ColCaption), Length(ColCaption), Size); + GetTextExtentPoint32W(DeviceContext, PWideChar(ColCaption), Length(ColCaption), TextSize); TextSpace := TextArea.Right - TextArea.Left; - if TextSpace < Size.cx then - ColCaption := VirtualTrees.Utils.ShortenString(DC, ColCaption, TextSpace); + if TextSpace < TextSize.cx then + ColCaption := VirtualTrees.Utils.ShortenString(DeviceContext, ColCaption, TextSpace); end; - SetBkMode(DC, TRANSPARENT); - SetTextColor(DC, ColorToRGB(clWindowText)); - DrawFormat := DT_TOP or DT_NOPREFIX or DT_LEFT; - DrawTextW(DC, PWideChar(ColCaption), Length(ColCaption), TextArea, DrawFormat); + SetTextColor(DeviceContext, ColorToRGB(clWindowText)); + DrawTextW(DeviceContext, PWideChar(ColCaption), Length(ColCaption), TextArea, DrawFormat); end; // Draw image, if any @@ -5827,9 +5938,9 @@ procedure TMainForm.AnyGridAdvancedHeaderDraw(Sender: TVTHeader; // Paint sort icon and number if hpeOverlay in Elements then begin SortArea := PaintArea; + Inc(SortArea.Left, SortArea.Width - Sender.Images.Width); GetSortIndex(PaintInfo.Column, ColSortIndex, ColSortDirection); if ColSortIndex > -1 then begin - Inc(SortArea.Left, SortArea.Width - Sender.Images.Width); // Prepare default font size, also if user selected a bigger one for the grid - we reserved a 16x16 space. // Font.Height + Font.Size must be set with these values to get this working, larger or smaller Size/Height // result in wrong size for multiple sort columns. @@ -5861,9 +5972,9 @@ procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: RefreshingData, IsKeyColumn, NeedFullColumns: Boolean; i, ColWidth, VisibleColumns, MaximumRows, FullColumnCount: Integer; ColMaxLen, Offset: Int64; - KeyCols, ColWidths, WantedColumnOrgnames: TStringList; - WantedColumns: TTableColumnList; - c: TTableColumn; + ColWidths, WantedColumnOrgnames: TStringList; + KeyCols, WantedColumns: TTableColumnList; + c, ColumnInKey: TTableColumn; OldScrollOffset: TPoint; DBObj: TDBObject; rx: TRegExpr; @@ -5877,7 +5988,7 @@ procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: col := vt.Header.Columns.Add; col.Text := TblCol.Name; col.Hint := TblCol.Comment; - col.Options := col.Options + [coSmartResize]; + col.Options := col.Options + [coSmartResize, coEditable]; if DatagridHiddenColumns.IndexOf(TblCol.Name) > -1 then col.Options := col.Options - [coVisible]; // Column header icon @@ -5910,6 +6021,8 @@ procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: DBObj.Connection.Ping(True); if SelectedTableColumns.Count = 0 then begin + vt.Header.Columns.Clear; + vt.Clear; EnableDataTab(False); end else begin EnableDataTab(True); @@ -5943,7 +6056,8 @@ procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: NeedFullColumns := False; for i:=0 to SelectedTableColumns.Count-1 do begin c := SelectedTableColumns[i]; - IsKeyColumn := KeyCols.IndexOf(c.Name) > -1; + ColumnInKey := KeyCols.FindByName(c.Name); + IsKeyColumn := Assigned(ColumnInKey); ColMaxLen := StrToInt64Def(c.LengthSet, 0); if (DatagridHiddenColumns.IndexOf(c.Name) = -1) or (IsKeyColumn) @@ -5955,7 +6069,7 @@ procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: and (not IsKeyColumn) // We need full length of any key column, so DataGridLoadFullRow() has the chance to fetch the right row and ((ColMaxLen > GRIDMAXDATA) or (ColMaxLen = 0)) // No need to blow SQL with LEFT() if column is shorter anyway then begin - Select := Select + DBObj.Connection.GetSQLSpecifity(spFuncLeft, [c.CastAsText, GRIDMAXDATA]) + ', '; + Select := Select + DBObj.Connection.SqlProvider.GetSql(qFuncLeft, [c.CastAsText, GRIDMAXDATA]) + ', '; end else if DBObj.Connection.Parameters.IsAnyMSSQL and (c.DataType.Index=dbdtTimestamp) then begin Select := Select + ' CAST(' + DBObj.Connection.QuoteIdent(c.Name) + ' AS INT), '; end else if DBObj.Connection.Parameters.IsAnyMSSQL and (c.DataType.Index=dbdtHierarchyid) then begin @@ -6058,7 +6172,7 @@ procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: Col.Text := '#'; for i:=0 to WantedColumns.Count-1 do begin InitColumn(i, WantedColumns[i]); - if coVisible in vt.Header.Columns[i].Options then + if coVisible in vt.Header.Columns[i+1].Options then Inc(VisibleColumns); end; @@ -6087,6 +6201,7 @@ procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: end; vt.EndUpdate; + ApplyFontToGrids; // Do not steel filter while writing filters if not SynMemoFilter.Focused then @@ -6104,10 +6219,12 @@ procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: if (FDataGridLastClickedColumnHeader >= 0) and (FDataGridLastClickedColumnHeader < vt.Header.Columns.Count) then begin // See issue #3309 // Horizontal offset based on the left side of a just sorted column - OldScrollOffset.X := -(vt.Header.Columns[FDataGridLastClickedColumnHeader].Left - FDataGridLastClickedColumnLeftPos); + OldScrollOffset.X := -(vt.Header.Columns[FDataGridLastClickedColumnHeader].Left - vt.OffsetX - FDataGridLastClickedColumnLeftPos); // logsql('Fixing x-offset to '+OldScrollOffset.X.ToString + // ', FDataGridLastClickedColumnHeader:'+FDataGridLastClickedColumnHeader.ToString + - // ', FDataGridLastClickedColumnLeftPos: '+FDataGridLastClickedColumnLeftPos.ToString); + // ', FDataGridLastClickedColumnLeftPos: '+FDataGridLastClickedColumnLeftPos.ToString + + // ', vt.Header.Columns[FDataGridLastClickedColumnHeader].Left: '+vt.Header.Columns[FDataGridLastClickedColumnHeader].Left.ToString + // ); end; vt.OffsetXY := OldScrollOffset; @@ -6139,7 +6256,7 @@ procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: procedure TMainForm.DataGridColumnResize(Sender: TVTHeader; Column: TColumnIndex); begin // Remember current table after last column resizing so we can auto size them as long as this did not happen - if not (tsUpdating in TBaseVirtualTree(Sender.Treeview).TreeStates) then + if not TBaseVirtualTree(Sender.Treeview).IsUpdating then FDataGridColumnWidthsCustomized := True; end; @@ -6166,52 +6283,57 @@ procedure TMainForm.DisplayRowCountStats(Sender: TBaseVirtualTree); cap := ActiveDatabase + '.' + DBObject.Name; IsLimited := DataGridWantedRowCount <= Datagrid.RootNodeCount; IsFiltered := SynMemoFilter.GetTextLen > 0; - if DBObject.NodeType = lntTable then begin - if (not IsLimited) and (not IsFiltered) then begin - RowsTotal := DataGrid.RootNodeCount; // No need to fetch via SHOW TABLE STATUS - DBObject.RowsAreExact := True; - menuQueryExactRowCount.Enabled := False; - end - else begin - Screen.Cursor := crHourGlass; - if (not DBObject.RowsAreExact) or FExactRowCountMode then - RowsTotal := DBObject.RowCount(True, FExactRowCountMode) - else - RowsTotal := DBObject.Rows; - Screen.Cursor := crDefault; - menuQueryExactRowCount.Enabled := True; - end; - if RowsTotal > -1 then begin - cap := cap + ': ' + FormatNumber(RowsTotal) + ' ' + _('rows total'); - if DBObject.Engine = 'InnoDB' then begin - if DBObject.RowsAreExact then - cap := cap + ' ('+_('exact')+')' - else - cap := cap + ' ('+_('approximately')+')'; - end; - // Display either LIMIT or WHERE effect, not both at the same time - if IsLimited then - cap := cap + ', '+_('limited to') + ' ' + FormatNumber(Datagrid.RootNodeCount) - else if IsFiltered then begin - if Datagrid.RootNodeCount = RowsTotal then - cap := cap + ', '+_('all rows match to filter') + case DBObject.NodeType of + lntTable: begin + if (not IsLimited) and (not IsFiltered) then begin + RowsTotal := DataGrid.RootNodeCount; // No need to fetch via SHOW TABLE STATUS + DBObject.RowsAreExact := True; + menuQueryExactRowCount.Enabled := False; + end + else begin + Screen.Cursor := crHourGlass; + if (not DBObject.RowsAreExact) or menuQueryExactRowCount.Checked then + RowsTotal := DBObject.RowCount(True, menuQueryExactRowCount.Checked) else - cap := cap + ', ' + FormatNumber(Datagrid.RootNodeCount) + ' '+_('rows match to filter'); - end; - // Update cached object reference with new row count, which may enable "Data" option - // in table copy dialog. See issue #666 - if Assigned(DBtree.FocusedNode) then begin - ObjInCache := DBtree.GetNodeData(DBtree.FocusedNode); - if Assigned(ObjInCache) and ObjInCache.IsSameAs(DBObject) then begin - ObjInCache.Rows := RowsTotal; - ObjInCache.RowsAreExact := DBObject.RowsAreExact; + RowsTotal := DBObject.Rows; + Screen.Cursor := crDefault; + menuQueryExactRowCount.Enabled := True; + end; + if RowsTotal > -1 then begin + cap := cap + ': ' + FormatNumber(RowsTotal) + ' ' + _('rows total'); + if DBObject.Engine = 'InnoDB' then begin + if DBObject.RowsAreExact then + cap := cap + ' ('+_('exact')+')' + else + cap := cap + ' ('+_('approximately')+')'; + end; + // Display either LIMIT or WHERE effect, not both at the same time + if IsLimited then + cap := cap + ', '+_('limited to') + ' ' + FormatNumber(Datagrid.RootNodeCount) + else if IsFiltered then begin + if Datagrid.RootNodeCount = RowsTotal then + cap := cap + ', '+_('all rows match to filter') + else + cap := cap + ', ' + FormatNumber(Datagrid.RootNodeCount) + ' '+_('rows match to filter'); + end; + // Update cached object reference with new row count, which may enable "Data" option + // in table copy dialog. See issue #666 + if Assigned(DBtree.FocusedNode) then begin + ObjInCache := DBtree.GetNodeData(DBtree.FocusedNode); + if Assigned(ObjInCache) and ObjInCache.IsSameAs(DBObject) then begin + ObjInCache.Rows := RowsTotal; + ObjInCache.RowsAreExact := DBObject.RowsAreExact; + end; end; end; end; + + lntView: begin + cap := cap + ': ' + FormatNumber(DataGrid.RootNodeCount) + ' ' + _('rows'); + end; end; lblDataTop.Caption := cap; lblDataTop.Hint := cap; - FExactRowCountMode := False; end; @@ -6219,7 +6341,6 @@ procedure TMainForm.menuQueryExactRowCountClick(Sender: TObject); begin // Activate exact row count mode and let DisplayRowCountStats do the rest // See https://www.heidisql.com/forum.php?t=41310 - FExactRowCountMode := True; DisplayRowCountStats(DataGrid); end; @@ -6230,10 +6351,14 @@ procedure TMainForm.AnyGridInitNode(Sender: TBaseVirtualTree; ParentNode, Node: Idx: PInt64; begin // Display multiline grid rows + // Mark all nodes as multiline capable. Fixes painting issues with long lines. (?) + // See issue #1897 and https://www.heidisql.com/forum.php?t=41502 + // Laggy performance with large grid contents (?) if AppSettings.ReadInt(asGridRowLineCount) = 1 then Exclude(Node.States, vsMultiLine) else Include(Node.States, vsMultiLine); + Sender.NodeHeight[Node] := TVirtualStringTree(Sender).DefaultNodeHeight; // Node may have data already, if added via InsertRow if not (vsOnFreeNodeCallRequired in Node.States) then begin Idx := Sender.GetNodeData(Node); @@ -6548,6 +6673,7 @@ procedure TMainForm.ValidateControls(Sender: TObject); actDataCancelChanges.Enabled := HasConnection and GridHasChanges; actDataSaveBlobToFile.Enabled := HasConnection and inDataOrQueryTabNotEmpty and Assigned(Grid.FocusedNode); actGridEditFunction.Enabled := HasConnection and inDataOrQueryTabNotEmpty and Assigned(Grid.FocusedNode); + actDataEditWithoutLookup.Enabled := HasConnection and inDataTab; actDataPreview.Enabled := HasConnection and inDataOrQueryTabNotEmpty and Assigned(Grid.FocusedNode); actDataOpenUrl.Enabled := (Length(CellText) ER_NO_SUCH_THREAD then @@ -6677,10 +6803,24 @@ procedure TMainForm.SynCompletionProposalChange(Sender: TObject; AIndex: Integer); var Proposal: TSynCompletionProposal; + SelectedFuncName: String; + SQLFunc: TSQLFunction; begin Proposal := Sender as TSynCompletionProposal; - if (AIndex >= 0) and (AIndex < Proposal.ItemList.Count) then + if (AIndex >= 0) and (AIndex < Proposal.ItemList.Count) then begin Proposal.Title := Proposal.InsertItem(AIndex); + // Show function description in hint panel + ShowStatusMsg('', 0); + SelectedFuncName := RegExprGetMatch('}function\\column\{\}\\color\{\w+\}([^\\]+)\\', Proposal.DisplayItem(AIndex), 1); + if not SelectedFuncName.IsEmpty then begin + for SQLFunc in ActiveConnection.SQLFunctions do begin + if SQLFunc.Name.ToUpper = SelectedFuncName.ToUpper then begin + ShowStatusMsg(SQLFunc.Description.Replace(SLineBreak, ' '), 0); + Break; + end; + end; + end; + end; end; @@ -6720,6 +6860,8 @@ procedure TMainForm.SynCompletionProposalCodeCompletion(Sender: TObject; end; rx.Free; Proposal.Form.CurrentEditor.UndoList.AddGroupBreak; + // Hide hint text added in .OnChange event + ShowStatusMsg('', 0); end; @@ -6743,11 +6885,12 @@ procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType; i, j, ImageIndex, ColumnsInList: Integer; Results: TDBQuery; DBObjects: TDBObjectList; - CurrentQuery, TableClauses, TableName, LeftPart, Token1, Token2, Token3, Token, Ident: String; + CurrentQuery, TableClauses, TableName, LeftPart, Token1, Token2, Token3, Ident: String; Tables: TStringList; rx: TRegExpr; - Start, TokenTypeInt: Integer; - Attri: TSynHighlighterAttributes; + CaretToken: String; + CaretStart, CaretTokenTypeInt: Integer; + CaretAttri: TSynHighlighterAttributes; Proposal: TSynCompletionProposal; Editor: TCustomSynEdit; Queries: TSQLBatch; @@ -6788,12 +6931,15 @@ procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType; dbname, tblname: String; Columns: TTableColumnList; Col: TTableColumn; + Keys: TTableKeyList; + Key: TTableKey; Obj: TDBObject; + ColumnIcon: Integer; begin dbname := ''; tblname := LeftToken; if Pos('.', tblname) > -1 then begin - dbname := Copy(tblname, 0, Pos('.', tblname)-1); + dbname := Copy(tblname, 1, Pos('.', tblname)-1); tblname := Copy(tblname, Pos('.', tblname)+1, Length(tblname)); end; // db and table name may already be quoted @@ -6805,9 +6951,22 @@ procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType; for Obj in DBObjects do begin if (Obj.Name.ToLowerInvariant = tblname.ToLowerInvariant) and (Obj.NodeType in [lntTable, lntView]) then begin Columns := Obj.TableColumns; + Keys := Obj.TableKeys; for Col in Columns do begin - DisplayText := SynCompletionProposalPrettyText(ICONINDEX_FIELD, LowerCase(Col.DataType.Name), Col.Name, '', DatatypeCategories[Col.DataType.Category].NullColor); - Proposal.AddItem(DisplayText, Col.Name); + // Detect index icon, if any + ColumnIcon := ICONINDEX_FIELD; + for Key in Keys do begin + if Key.Columns.Contains(Col.Name) then begin + ColumnIcon := Key.ImageIndex; + Break; + end; + end; + // Put formatted text and icon into proposal + DisplayText := SynCompletionProposalPrettyText(ColumnIcon, LowerCase(Col.DataType.Name), Col.Name, Col.Comment, DatatypeCategories[Col.DataType.Category].NullColor); + if CurrentInput.StartsWith(Conn.QuoteChar) then + Proposal.AddItem(DisplayText, Conn.QuoteChar + Col.Name) + else + Proposal.AddItem(DisplayText, Col.Name); Inc(ColumnsInList); end; Columns.Free; @@ -6819,15 +6978,16 @@ procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType; begin Proposal := Sender as TSynCompletionProposal; Proposal.Font.Assign(Font); + Proposal.TitleFont.Size := Proposal.Font.Size; Proposal.ItemHeight := ScaleSize(PROPOSAL_ITEM_HEIGHT); Proposal.ClearList; Proposal.Columns[0].ColumnWidth := ScaleSize(100); // Kind of random value, but fits well Proposal.Columns[1].ColumnWidth := ScaleSize(100); Conn := ActiveConnection; Editor := Proposal.Form.CurrentEditor; - Editor.GetHighlighterAttriAtRowColEx(Editor.PrevWordPos, Token, TokenTypeInt, Start, Attri); + Editor.GetHighlighterAttriAtRowColEx(Editor.CaretXY, CaretToken, CaretTokenTypeInt, CaretStart, CaretAttri); CanExecute := AppSettings.ReadBool(asCompletionProposal) and - (not (TtkTokenKind(TokenTypeInt) in [SynHighlighterSQL.tkString, SynHighlighterSQL.tkComment])); + (not (TtkTokenKind(CaretTokenTypeInt) in [SynHighlighterSQL.tkString, SynHighlighterSQL.tkComment])); if not CanExecute then Exit; @@ -6837,7 +6997,7 @@ procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType; rx := TRegExpr.Create; // Find token1.token2.token3, while cursor is somewhere in token3 - Ident := '[^\s,\(\)=\.]'; + Ident := '[^\s,\(\)=\.\!<>]'; rx.Expression := '(('+Ident+'+)\.)?('+Ident+'+)\.('+Ident+'*)$'; LeftPart := Copy(Editor.LineText, 1, Editor.CaretX-1); if rx.Exec(LeftPart) then begin @@ -6872,7 +7032,7 @@ procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType; CurrentQuery := 'SELECT * FROM '+ActiveDbObj.QuotedName+' WHERE ' + Editor.Text; end else begin // In a query tab - Queries := TSQLBatch.Create; + Queries := TSQLBatch.Create(Conn.Parameters.NetTypeGroup); Queries.SQL := Editor.Text; for Query in Queries do begin if (Query.LeftOffset <= Editor.SelStart) and (Editor.SelStart < Query.RightOffset) then begin @@ -6890,7 +7050,7 @@ procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType; if rx.Exec(CurrentQuery) then begin TableClauses := rx.Match[3]; // Ensure tables in JOIN clause(s) are splitted by comma - TableClauses := StringReplace(TableClauses, 'JOIN', ',', [rfReplaceAll, rfIgnoreCase]); + TableClauses := ReplaceRegExpr('\sJOIN\s', TableClauses, ',', [rroModifierI]); // Remove surrounding parentheses TableClauses := StringReplace(TableClauses, '(', ' ', [rfReplaceAll]); TableClauses := StringReplace(TableClauses, ')', ' ', [rfReplaceAll]); @@ -7043,7 +7203,7 @@ procedure TMainForm.SynMemoSQLLogSpecialLineColors(Sender: TObject; Edit: TSynMemo; LineText, Search: String; begin - // Paint error line with red background + // Paint error line with red background, or warning in orange Edit := Sender as TSynMemo; LineText := Copy(Edit.Lines[Line-1], 1, 100); Search := _(MsgSQLError); @@ -7053,6 +7213,21 @@ procedure TMainForm.SynMemoSQLLogSpecialLineColors(Sender: TObject; Special := True; FG := ErrorLineForeground; BG := ErrorLineBackground; + end + else if LineText.Contains(_(SLogPrefixWarning)+':') then begin + Special := True; + FG := WarningLineForeground; + BG := WarningLineBackground; + end + else if LineText.Contains(_(SLogPrefixNote)+':') then begin + Special := True; + FG := NoteLineForeground; + BG := NoteLineBackground; + end + else if LineText.Contains(_(SLogPrefixInfo)+':') then begin + Special := True; + FG := InfoLineForeground; + BG := InfoLineBackground; end; end; @@ -7097,6 +7272,116 @@ procedure TMainForm.SynMemoQueryStatusChange(Sender: TObject; Changes: TSynStatu end; +procedure TMainForm.SynMemoQueryTokenHint(Sender: TObject; Coords: TBufferCoord; + const Token: string; TokenType: Integer; Attri: TSynHighlighterAttributes; + var HintText: string); +var + SQLFunc: TSQLFunction; + Conn: TDBConnection; + AllObjects: TDBObjectList; + Obj: TDBObject; + i, ColumnNameChars: Integer; + Column: TTableColumn; + Parameters: TRoutineParamList; + Params: TStringList; + Param: TRoutineParam; +begin + // Activate hint for SQL function in query editors + Conn := ActiveConnection; + if Assigned(Conn) then begin + case TtkTokenKind(TokenType) of + + SynHighlighterSQL.tkFunction: begin + for SQLFunc in ActiveConnection.SQLFunctions do begin + if SQLFunc.Name.ToUpper = Token.ToUpper then begin + HintText := SQLFunc.Name + SQLFunc.Declaration + sLineBreak + sLineBreak + SQLFunc.Description; + Break; + end; + end; + end; + + SynHighlighterSQL.tkTableName: begin + // Show some details from table listing cache + if (not Conn.IsLockedByThread) and Conn.DbObjectsCached(Conn.Database) then begin + AllObjects := Conn.GetDBObjects(Conn.Database); + for Obj in AllObjects do begin + if (Obj.NodeType = lntTable) and (Obj.Name.ToLower = Token.ToLower) then begin + HintText := _(Obj.ObjType) + ' ' + Obj.Name + ':' + sLineBreak + + _('Rows') + ': ' + FormatNumber(Obj.Rows) + sLineBreak + + _('Size') + ': ' + FormatByteNumber(Obj.DataLen + Obj.IndexLen) + SLineBreak; + ColumnNameChars := 0; + for Column in Obj.TableColumns do begin + ColumnNameChars := Max(ColumnNameChars, Length(Column.Name)); + end; + for Column in Obj.TableColumns do begin + HintText := HintText + Format('%s%'+ColumnNameChars.ToString+'s: %s', [SLineBreak, Column.Name, Column.FullDataType]); + end; + + Break; + end; + end; + end; + end; + + SynHighlighterSQL.tkProcName: begin + // Show routine parameters, comment and body + if (not Conn.IsLockedByThread) and Conn.DbObjectsCached(Conn.Database) then begin + AllObjects := Conn.GetDBObjects(Conn.Database); + for Obj in AllObjects do begin + if (Obj.NodeType in [lntFunction, lntProcedure]) and (Obj.Name.ToLower = Token.ToLower) then begin + Parameters := TRoutineParamList.Create; + Conn.ParseRoutineStructure(Obj, Parameters); + HintText := _(Obj.ObjType) + ' ' + Obj.Name; + Params := TStringList.Create; + for Param in Parameters do begin + Params.Add(Param.Name + ' ['+Param.Datatype+']'); + end; + HintText := HintText + '(' + Implode(', ', Params) + ')' + sLineBreak + sLineBreak; + Params.Free; + if not Obj.Returns.IsEmpty then + HintText := HintText + 'Returns: ' + Obj.Returns + sLineBreak + sLineBreak; + if not Obj.Comment.IsEmpty then + HintText := HintText + Obj.Comment + sLineBreak + sLineBreak; + if not Obj.Body.IsEmpty then + HintText := HintText + StrEllipsis(Obj.Body, SIZE_KB); + HintText := Trim(HintText); + Break; + end; + end; + end; + end; + + SynHighlighterSQL.tkDatatype: begin + for i:=Low(Conn.Datatypes) to High(Conn.Datatypes) do begin + if Conn.Datatypes[i].Name.ToLower = Token.ToLower then begin + HintText := WrapText(Conn.Datatypes[i].Description, 100); + Break; + end; + end; + end; + + { Keywords consist of more than one word too often, so this would be of zero help for the user: + SynHighlighterSQL.tkKey: begin + if Conn.Parameters.IsAnyMySQL then begin + if not Assigned(FHelpData) then + FHelpData := TSimpleKeyValuePairs.Create; + if not FHelpData.TryGetValue(Token, HintText) then begin + HintText := Conn.GetVar('HELP '+Conn.EscapeString(Token), 1); + if (HintText.ToUpper = 'Y') or (HintText.ToUpper = 'N') then + HintText := ''; + FHelpData.Add(Token, HintText); + end; + end; + end; } + + SynHighlighterSQL.tkString: begin + HintText := _('String:') + ' ' + FormatByteNumber(Length(Token)); + end; + + end; + end; +end; + procedure TMainForm.TimerHostUptimeTimer(Sender: TObject); var Conn: TDBConnection; @@ -7164,9 +7449,9 @@ procedure TMainForm.ListTablesNewText(Sender: TBaseVirtualTree; Node: // rename table case Obj.NodeType of lntTable: - sql := Obj.Connection.GetSQLSpecifity(spRenameTable); + sql := Obj.Connection.SqlProvider.GetSql(qRenameTable); lntView: - sql := Obj.Connection.GetSQLSpecifity(spRenameView); + sql := Obj.Connection.SqlProvider.GetSql(qRenameView); else raise EDbError.Create('Cannot rename '+Obj.ObjType); end; @@ -7209,11 +7494,15 @@ procedure TMainForm.TimerConnectedTimer(Sender: TObject); procedure TMainForm.Copylinetonewquerytab1Click(Sender: TObject); var Tab: TQueryTab; + LineText: String; begin // Create new query tab with current line in SQL log. This is for lazy mouse users. if actNewQueryTab.Execute then begin Tab := QueryTabs[MainForm.QueryTabs.Count-1]; - Tab.Memo.Text := SynMemoSQLLog.LineText; + LineText := SynMemoSQLLog.LineText; + if AppSettings.ReadBool(asLogTimestamp) then + LineText := ReplaceRegExpr('^\s*\[[^\]]+\]\s', LineText, ''); + Tab.Memo.Text := LineText; end; end; @@ -7226,6 +7515,7 @@ procedure TMainForm.QuickFilterClick(Sender: TObject); Item: TMenuItem; Conn: TDBConnection; ShiftKeyPressed: Boolean; + OldDataLocalNumberFormat: Boolean; begin // Set filter for "where..."-clause if (PageControlMain.ActivePage <> tabData) or (DataGrid.FocusedColumn = NoColumn) then @@ -7241,24 +7531,29 @@ procedure TMainForm.QuickFilterClick(Sender: TObject); if ExecRegExpr('Prompt\d+$', Act.Name) then begin // Item needs prompt TableCol := SelectedTableFocusedColumn; - Col := Conn.QuoteIdent(TableCol.Name, False); - - if (TableCol.DataType.Index = dbdtJson) - and (Conn.Parameters.NetTypeGroup = ngPgSQL) then begin - Col := Col + '::text'; - end; - Val := DataGrid.Text[DataGrid.FocusedNode, DataGrid.FocusedColumn]; - if InputQuery(_('Specify filter-value...'), Act.Caption, Val) then begin - if Act = actQuickFilterPrompt1 then - Filter := Col + ' = ' + Conn.EscapeString(Val, TableCol.DataType) - else if Act = actQuickFilterPrompt2 then - Filter := Col + ' != ' + Conn.EscapeString(Val, TableCol.DataType) - else if Act = actQuickFilterPrompt3 then - Filter := Col + ' > ' + Conn.EscapeString(Val, TableCol.DataType) - else if Act = actQuickFilterPrompt4 then - Filter := Col + ' < ' + Conn.EscapeString(Val, TableCol.DataType) - else if Act = actQuickFilterPrompt5 then - Filter := Conn.GetSQLSpecifity(spLikeCompare, [Col, Conn.EscapeString('%'+Val+'%', TableCol.DataType)]); + if Assigned(TableCol) then begin + Col := Conn.QuoteIdent(TableCol.Name, False); + + if (TableCol.DataType.Index = dbdtJson) + and (Conn.Parameters.NetTypeGroup = ngPgSQL) then begin + Col := Col + '::text'; + end; + OldDataLocalNumberFormat := DataLocalNumberFormat; + DataLocalNumberFormat := False; + Val := DataGrid.Text[DataGrid.FocusedNode, DataGrid.FocusedColumn]; + DataLocalNumberFormat := OldDataLocalNumberFormat; + if InputQuery(_('Specify filter-value...'), Act.Caption, Val) then begin + if Act = actQuickFilterPrompt1 then + Filter := Col + ' = ' + Conn.EscapeString(Val, TableCol.DataType) + else if Act = actQuickFilterPrompt2 then + Filter := Col + ' != ' + Conn.EscapeString(Val, TableCol.DataType) + else if Act = actQuickFilterPrompt3 then + Filter := Col + ' > ' + Conn.EscapeString(Val, TableCol.DataType) + else if Act = actQuickFilterPrompt4 then + Filter := Col + ' < ' + Conn.EscapeString(Val, TableCol.DataType) + else if Act = actQuickFilterPrompt5 then + Filter := Conn.SqlProvider.GetSql(qLikeCompare, [Col, Conn.EscapeString('%'+Val+'%', TableCol.DataType)]); + end; end; end else begin @@ -7411,7 +7706,7 @@ procedure TMainForm.SynMemoQueryDragDrop(Sender, Source: TObject; X, lntTable..lntEvent: begin if ShiftPressed then Text := ActiveDbObj.QuotedDatabase(False) + '.'; - Text := Text + ActiveDbObj.QuotedName(False); + Text := Text + ActiveDbObj.Connection.QuoteIdent(ActiveDbObj.Name, False); end; end; end else if src = Tree then begin @@ -7419,7 +7714,7 @@ procedure TMainForm.SynMemoQueryDragDrop(Sender, Source: TObject; X, 1: case Tree.FocusedNode.Parent.Index of TQueryTab.HelperNodeSnippets: - Text := ReadTextFile(AppSettings.DirnameSnippets + Tree.Text[Tree.FocusedNode, 0] + '.sql', nil); + Text := ReadTextFile(AppSettings.DirnameSnippets + Tree.Text[Tree.FocusedNode, 0] + FILEEXT_SNIPPET, nil); TQueryTab.HelperNodeHistory: Text := ''; else begin @@ -7462,18 +7757,10 @@ procedure TMainForm.SynMemoQueryDragDrop(Sender, Source: TObject; X, procedure TMainForm.SynMemoQueryDropFiles(Sender: TObject; X, Y: Integer; AFiles: TUnicodeStrings); -var - i: Integer; - Tab: TQueryTab; begin // One or more files from explorer or somewhere else was dropped onto the // query-memo - load their contents into seperate tabs - if not RunQueryFiles(AFiles, nil, False) then begin - for i:=0 to AFiles.Count-1 do begin - Tab := GetOrCreateEmptyQueryTab(True); - Tab.LoadContents(AFiles[i], False, nil); - end; - end; + OpenQueryFiles(AFiles, nil, False); end; @@ -7760,7 +8047,7 @@ procedure TMainForm.popupHostPopup(Sender: TObject); menuFetchDBitems.Enabled := (PageControlHost.ActivePage = tabDatabases) and (ListDatabases.SelectedCount > 0); Kill1.Enabled := (PageControlHost.ActivePage = tabProcessList) and (ListProcesses.SelectedCount > 0); menuEditVariable.Enabled := False; - if ActiveConnection.ServerVersionInt >= 40003 then + if ActiveConnection.Has(frEditVariables) then menuEditVariable.Enabled := (PageControlHost.ActivePage = tabVariables) and Assigned(ListVariables.FocusedNode) else menuEditVariable.Hint := _(SUnsupported); @@ -7770,10 +8057,13 @@ procedure TMainForm.popupHostPopup(Sender: TObject); procedure TMainForm.popupDBPopup(Sender: TObject); var Obj: PDBObject; - HasFocus, IsDb, IsObject: Boolean; - Version: Integer; + IsDb, IsObject: Boolean; + Conn: TDBConnection; begin // DBtree and ListTables both use popupDB as menu + actQueryTable.Caption := f_('Select top %s rows', [FormatNumber(AppSettings.ReadInt(asDatagridRowsPerStep))]); + actQueryTable.Hint := f_('Selects the first %s rows in a new query tab', [FormatNumber(AppSettings.ReadInt(asDatagridRowsPerStep))]); + if PopupComponent(Sender) = DBtree then begin Obj := DBTree.GetNodeData(DBTree.FocusedNode); IsDb := Obj.NodeType = lntDb; @@ -7795,15 +8085,16 @@ procedure TMainForm.popupDBPopup(Sender: TObject); actDetachDatabase.Enabled := actDetachDatabase.Visible and (Obj.NodeType = lntDb); actCopyTable.Enabled := Obj.NodeType in [lntTable, lntView]; actEmptyTables.Enabled := Obj.NodeType in [lntTable, lntView]; + actQueryTable.Enabled := Obj.NodeType in [lntTable, lntView]; actRunRoutines.Enabled := Obj.NodeType in [lntProcedure, lntFunction]; menuClearDataTabFilter.Enabled := Obj.NodeType in [lntTable, lntView]; - menuEditObject.Enabled := IsDb or IsObject; + menuEditObject.Enabled := (IsDb and Obj.Connection.Parameters.IsAnyMySQL) or IsObject; // Enable certain items which are valid only here menuTreeExpandAll.Enabled := True; menuTreeCollapseAll.Enabled := True; menuTreeOptions.Enabled := True; end else begin - HasFocus := Assigned(ListTables.FocusedNode); + Obj := ListTables.GetNodeData(ListTables.FocusedNode); actCreateDatabase.Enabled := False; actConnectionProperties.Enabled := False; actAttachDatabase.Visible := False; @@ -7816,25 +8107,22 @@ procedure TMainForm.popupDBPopup(Sender: TObject); actDropObjects.Enabled := ListTables.SelectedCount > 0; actDetachDatabase.Visible := False; actEmptyTables.Enabled := True; + actQueryTable.Enabled := Assigned(Obj) and (Obj.NodeType in [lntTable, lntView]); actRunRoutines.Enabled := True; menuClearDataTabFilter.Enabled := False; - menuEditObject.Enabled := HasFocus; - actCopyTable.Enabled := False; - if HasFocus then begin - Obj := ListTables.GetNodeData(ListTables.FocusedNode); - actCopyTable.Enabled := Obj.NodeType in [lntTable, lntView]; - end; + menuEditObject.Enabled := Assigned(Obj); + actCopyTable.Enabled := Assigned(Obj) and (Obj.NodeType in [lntTable, lntView]); menuTreeExpandAll.Enabled := False; menuTreeCollapseAll.Enabled := False; menuTreeOptions.Enabled := False; end; - if (ActiveConnection <> nil) and (ActiveConnection.Parameters.IsAnyMySQL) then begin - Version := ActiveConnection.ServerVersionInt; - actCreateView.Enabled := actCreateView.Enabled and (Version >= 50001); - actCreateProcedure.Enabled := actCreateProcedure.Enabled and (Version >= 50003); - actCreateFunction.Enabled := actCreateFunction.Enabled and (Version >= 50003); - actCreateTrigger.Enabled := actCreateTrigger.Enabled and (Version >= 50002); - actCreateEvent.Enabled := actCreateEvent.Enabled and (Version >= 50100); + Conn := ActiveConnection; + if (Conn <> nil) and (Conn.Parameters.IsAnyMySQL) then begin + actCreateView.Enabled := actCreateView.Enabled and Conn.Has(frCreateView); + actCreateProcedure.Enabled := actCreateProcedure.Enabled and Conn.Has(frCreateProcedure); + actCreateFunction.Enabled := actCreateFunction.Enabled and Conn.Has(frCreateFunction); + actCreateTrigger.Enabled := actCreateTrigger.Enabled and Conn.Has(frCreateTrigger); + actCreateEvent.Enabled := actCreateEvent.Enabled and Conn.Has(frCreateEvent); end; end; @@ -7947,13 +8235,13 @@ procedure TMainForm.popupDataGridPopup(Sender: TObject); actQuickFilterFocused1.Hint := actQuickFilterFocused1.Hint + Results.Connection.EscapeString(Value, Datatype) + ', '; actQuickFilterFocused2.Hint := actQuickFilterFocused2.Hint + Results.Connection.EscapeString(Value, Datatype) + ', '; actQuickFilterFocused3.Hint := actQuickFilterFocused3.Hint + - Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '''' + Results.Connection.EscapeString(Value, True, False) + '%''']) + + Results.Connection.SqlProvider.GetSql(qLikeCompare, [Col, '''' + Results.Connection.EscapeString(Value, True, False) + '%''']) + ' OR '; actQuickFilterFocused4.Hint := actQuickFilterFocused4.Hint + - Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '''%' + Results.Connection.EscapeString(Value, True, False) + '''']) + + Results.Connection.SqlProvider.GetSql(qLikeCompare, [Col, '''%' + Results.Connection.EscapeString(Value, True, False) + '''']) + ' OR '; actQuickFilterFocused5.Hint := actQuickFilterFocused5.Hint + - Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '''%' + Results.Connection.EscapeString(Value, True, False) + '%''']) + + Results.Connection.SqlProvider.GetSql(qLikeCompare, [Col, '''%' + Results.Connection.EscapeString(Value, True, False) + '%''']) + ' OR '; actQuickFilterFocused6.Hint := actQuickFilterFocused6.Hint + Col + ' > ' + Results.Connection.EscapeString(Value, Datatype) + ' OR '; actQuickFilterFocused7.Hint := actQuickFilterFocused7.Hint + Col + ' < ' + Results.Connection.EscapeString(Value, Datatype) + ' OR '; @@ -8006,7 +8294,7 @@ procedure TMainForm.popupDataGridPopup(Sender: TObject); actQuickFilterPrompt2.Hint := Col + ' != "..."'; actQuickFilterPrompt3.Hint := Col + ' > "..."'; actQuickFilterPrompt4.Hint := Col + ' < "..."'; - actQuickFilterPrompt5.Hint := Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '"%...%"']); + actQuickFilterPrompt5.Hint := Results.Connection.SqlProvider.GetSql(qLikeCompare, [Col, '"%...%"']); actQuickFilterNull.Hint := Col + ' IS NULL'; actQuickFilterNotNull.Hint := Col + ' IS NOT NULL'; @@ -8022,7 +8310,7 @@ procedure TMainForm.popupDataGridPopup(Sender: TObject); actQuickFilterClipboard4.Enabled := true; actQuickFilterClipboard4.Hint := Col + ' < ' + Results.Connection.EscapeString(Value, Datatype); actQuickFilterClipboard5.Enabled := true; - actQuickFilterClipboard5.Hint := Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '''%' + Results.Connection.EscapeString(Value, True, False) + '%''']); + actQuickFilterClipboard5.Hint := Results.Connection.SqlProvider.GetSql(qLikeCompare, [Col, '''%' + Results.Connection.EscapeString(Value, True, False) + '%''']); actQuickFilterClipboard6.Enabled := true; actQuickFilterClipboard6.Hint := Col + ' IN (' + Value + ')'; end else begin @@ -8035,7 +8323,7 @@ procedure TMainForm.popupDataGridPopup(Sender: TObject); actQuickFilterClipboard4.Enabled := false; actQuickFilterClipboard4.Hint := Col + ' < ' + CLPBRD; actQuickFilterClipboard5.Enabled := false; - actQuickFilterClipboard5.Hint := Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '%' + CLPBRD + '%']); + actQuickFilterClipboard5.Hint := Results.Connection.SqlProvider.GetSql(qLikeCompare, [Col, '%' + CLPBRD + '%']); actQuickFilterClipboard6.Enabled := false; actQuickFilterClipboard6.Hint := Col + ' IN (' + CLPBRD + ')'; end; @@ -8097,7 +8385,7 @@ procedure TMainForm.QFvaluesClick(Sender: TObject); ShowStatusMsg(_('Fetching distinct values ...')); DbObj := ActiveDbObj; Conn := DbObj.Connection; - MaxSize := SIZE_GB; + MaxSize := SIZE_GB*2; ColumnHasIndex := DataGridResult.ColIsKeyPart(ResultCol) or DataGridResult.ColIsUniqueKeyPart(ResultCol) or DataGridResult.ColIsPrimaryKeyPart(ResultCol); @@ -8111,7 +8399,7 @@ procedure TMainForm.QFvaluesClick(Sender: TObject); if SynMemoFilter.Text <> '' then Query := Query + ' WHERE ' + SynMemoFilter.Text + CRLF; Query := Query + ' GROUP BY '+Conn.QuoteIdent(ColName)+' ORDER BY c DESC, '+Conn.QuoteIdent(ColName); - Data := Conn.GetResults(Conn.ApplyLimitClause('SELECT', Query, 30, 0)); + Data := Conn.GetResults(Conn.ApplyLimitClause('SELECT', Query, 50, 0)); for i:=0 to Data.RecordCount-1 do begin if QFvalues.Count > i then Item := QFvalues[i] @@ -8184,7 +8472,7 @@ procedure TMainForm.DataInsertValueClick(Sender: TObject); begin // Local and UTC date/time menu items Conn := ActiveConnection; - DateTimeSQL := 'SELECT ' + Conn.GetSQLSpecifity(spFuncNow); + DateTimeSQL := 'SELECT ' + Conn.SqlProvider.GetSql(qFuncNow); LocalTime := Conn.ParseDateTime(Conn.GetVar(DateTimeSQL)); DecodeDateTime(LocalTime, y, m, d, h, i, s, ms); DataDateTime.Caption := Format(FrmDateTime, [_('Date and time'), y,m,d,h,i,s]); @@ -8489,33 +8777,65 @@ procedure TMainForm.insertFunction(Sender: TObject); end; -{** - Delete a snippet file -} -procedure TMainForm.menuDeleteSnippetClick(Sender: TObject); +procedure TMainForm.menuRenameSnippetClick(Sender: TObject); var - snippetfile : String; + OldName, NewName: String; begin - // Don't do anything if no item was selected if not Assigned(QueryTabs.ActiveHelpersTree.FocusedNode) then Exit; - snippetfile := AppSettings.DirnameSnippets + QueryTabs.ActiveHelpersTree.Text[QueryTabs.ActiveHelpersTree.FocusedNode, 0] + '.sql'; - if MessageDialog(_('Delete snippet file?'), snippetfile, mtConfirmation, [mbOk, mbCancel]) = mrOk then + OldName := QueryTabs.ActiveHelpersTree.Text[QueryTabs.ActiveHelpersTree.FocusedNode, 0]; + NewName := OldName; + if InputQuery(_('Rename'), 'Rename snippet "'+OldName+'"', NewName) then begin Screen.Cursor := crHourGlass; - if DeleteFileWithUndo(snippetfile) then begin - // Refresh list with snippets - SetSnippetFilenames; - end else begin + if NewName.IsEmpty then begin Screen.Cursor := crDefault; - ErrorDialog(f_('Failed deleting %s', [snippetfile])); + ErrorDialog(f_('Failed renaming %s', ['"' + OldName + '" => "' + NewName + '"'])); + end + else begin + OldName := ChangeFileExt(AppSettings.DirnameSnippets + OldName, FILEEXT_SNIPPET); + NewName := ChangeFileExt(AppSettings.DirnameSnippets + NewName, FILEEXT_SNIPPET); + LogSQL(Format('Rename snippet "%s" to "%s"', [OldName, NewName]), lcDebug); + if RenameFile(OldName, NewName) then begin + // Refresh list with snippets + SetSnippetFilenames; + end else begin + Screen.Cursor := crDefault; + ErrorDialog(f_('Failed renaming %s', ['"' + OldName + '" => "' + NewName + '"'])); + end; end; Screen.Cursor := crDefault; end; end; - +{** + Delete a snippet file +} +procedure TMainForm.menuDeleteSnippetClick(Sender: TObject); +var + SnippetFile : String; +begin + // Don't do anything if no item was selected + if not Assigned(QueryTabs.ActiveHelpersTree.FocusedNode) then + Exit; + + SnippetFile := AppSettings.DirnameSnippets + QueryTabs.ActiveHelpersTree.Text[QueryTabs.ActiveHelpersTree.FocusedNode, 0] + FILEEXT_SNIPPET; + if MessageDialog(_('Delete snippet file?'), SnippetFile, mtConfirmation, [mbOk, mbCancel]) = mrOk then + begin + Screen.Cursor := crHourGlass; + if DeleteFileWithUndo(SnippetFile) then begin + // Refresh list with snippets + SetSnippetFilenames; + end else begin + Screen.Cursor := crDefault; + ErrorDialog(f_('Failed deleting %s', [SnippetFile])); + end; + Screen.Cursor := crDefault; + end; +end; + + procedure TMainForm.menuDoubleClickInsertsNodeTextClick(Sender: TObject); var Item: TMenuItem; @@ -8546,7 +8866,7 @@ procedure TMainForm.menuInsertAtCursorClick(Sender: TObject); } procedure TMainForm.menuLoadSnippetClick(Sender: TObject); begin - QueryTabs.ActiveTab.LoadContents(AppSettings.DirnameSnippets + QueryTabs.ActiveHelpersTree.Text[QueryTabs.ActiveHelpersTree.FocusedNode, 0] + '.sql', True, nil); + QueryTabs.ActiveTab.LoadContents(AppSettings.DirnameSnippets + QueryTabs.ActiveHelpersTree.Text[QueryTabs.ActiveHelpersTree.FocusedNode, 0] + FILEEXT_SNIPPET, True, nil); end; @@ -8562,21 +8882,53 @@ procedure TMainForm.menuExploreClick(Sender: TObject); procedure TMainForm.menuClearQueryHistoryClick(Sender: TObject); var Values: TStringList; - PathToDelete: String; + HistoryRootKey, ValueNameToDelete: String; + Tab: TQueryTab; + ClickNode: PVirtualNode; + History: TQueryHistory; + DialogResult: TModalResult; begin // Clear query history items in registry - // Take care of MessageDialog, probably changing the current SessionPath - PathToDelete := ActiveConnection.Parameters.SessionPath + '\' + REGKEY_QUERYHISTORY; - AppSettings.SessionPath := PathToDelete; - Values := AppSettings.GetValueNames; - if MessageDialog(_('Clear query history?'), f_('%s history items will be deleted.', [FormatNumber(Values.Count)]), mtConfirmation, [mbYes, mbNo]) = mrYes then begin - Screen.Cursor := crHourglass; - AppSettings.SessionPath := PathToDelete; - AppSettings.DeleteCurrentKey; - RefreshHelperNode(TQueryTab.HelperNodeHistory); - Screen.Cursor := crDefault; + HistoryRootKey := ActiveConnection.Parameters.SessionPath + '\' + REGKEY_QUERYHISTORY; + AppSettings.SessionPath := HistoryRootKey; + + Tab := QueryTabs.ActiveTab; + ClickNode := Tab.treeHelpers.FocusedNode; + + case Tab.treeHelpers.GetNodeLevel(ClickNode) of + 1: begin + Values := AppSettings.GetValueNames; + //showmessage(Values.Text); + DialogResult := MessageDialog( + _('Clear query history?'), + f_('%s history items will be deleted.', [FormatNumber(Values.Count)]), + mtConfirmation, + [mbYes, mbNo] + ); + if DialogResult = mrYes then begin + Screen.Cursor := crHourglass; + // MessageDialog may have changed the current SessionPath + AppSettings.SessionPath := HistoryRootKey; + AppSettings.DeleteCurrentKey; + RefreshHelperNode(TQueryTab.HelperNodeHistory); + Screen.Cursor := crDefault; + end; + Values.Free; + end; + + 2: begin + History := Tab.HistoryDays.Objects[ClickNode.Parent.Index] as TQueryHistory; + ValueNameToDelete := History[ClickNode.Index].RegValue.ToString; + //showmessage(ValueNameToDelete); + Screen.Cursor := crHourglass; + // MessageDialog may have changed the current SessionPath + AppSettings.SessionPath := HistoryRootKey; + AppSettings.DeleteValue(ValueNameToDelete); + RefreshHelperNode(TQueryTab.HelperNodeHistory); + Screen.Cursor := crDefault; + end; end; - Values.Free; + AppSettings.ResetPath; end; @@ -8748,6 +9100,7 @@ procedure TMainForm.AnyGridGetHint(Sender: TBaseVirtualTree; Node: Tree: TVirtualStringTree; NewHint: String; Conn: TDBConnection; + ValIsNumber: Boolean; begin // Disable tooltips on Wine, as they prevent users from clicking + editing clipped cells if IsWine then @@ -8771,8 +9124,15 @@ procedure TMainForm.AnyGridGetHint(Sender: TBaseVirtualTree; Node: end; if HintText.IsEmpty then begin - HintText := Tree.Text[Node, Column]; - HintText := StrEllipsis(HintText, SIZE_KB); + try + ValIsNumber := IntToStr(MakeInt(Tree.Text[Node, Column])) = Tree.Text[Node, Column]; + except + ValIsNumber := False; + end; + if ValIsNumber then + HintText := FormatNumber(Tree.Text[Node, Column]) + else + HintText := StrEllipsis(Tree.Text[Node, Column], SIZE_KB); end; // See http://www.heidisql.com/forum.php?t=20458#p20548 if Sender = DBtree then @@ -8855,6 +9215,8 @@ procedure TMainForm.ListTablesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCa var Obj: PDBObject; begin + PaintAlternatingRowBackground(TargetCanvas, Node, CellRect); + // Only paint bar in rows + size column if Column in [1, 2] then begin Obj := Sender.GetNodeData(Node); @@ -8866,6 +9228,36 @@ procedure TMainForm.ListTablesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCa end; +function TMainForm.GetAlternatingRowBackground(Node: PVirtualNode): TColor; +var + clEven, clOdd: TColor; + isEven: Boolean; +begin + // Alternating row background. See issue #139 + Result := clNone; + clEven := AppSettings.ReadInt(asRowBackgroundEven); + clOdd := AppSettings.ReadInt(asRowBackgroundOdd); + isEven := Node.Index mod 2 = 0; + if IsEven and (clEven <> clNone) then + Result := clEven + else if (not IsEven) and (clOdd <> clNone) then + Result := clOdd; +end; + + +procedure TMainForm.PaintAlternatingRowBackground(TargetCanvas: TCanvas; Node: PVirtualNode; CellRect: TRect); +var + BgColor: TColor; +begin + // Apply color + BgColor := GetAlternatingRowBackground(Node); + if BgColor <> clNone then begin + TargetCanvas.Brush.Color := BgColor; + TargetCanvas.FillRect(CellRect); + end; +end; + + procedure TMainForm.PaintColorBar(Value, Max: Extended; TargetCanvas: TCanvas; CellRect: TRect); var BarWidth, CellWidth: Integer; @@ -9104,27 +9496,6 @@ procedure TMainForm.PrepareImageList; end; -procedure TMainForm.ListVariablesBeforeCellPaint(Sender: TBaseVirtualTree; - TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; - CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); -var - SessionVal, GlobalVal: String; - vt: TVirtualStringTree; -begin - // Highlight cell if session variable is different to global variable - vt := Sender as TVirtualStringTree; - if Column = 1 then begin - SessionVal := vt.Text[Node, 1]; - GlobalVal := vt.Text[Node, 2]; - if SessionVal <> GlobalVal then begin - TargetCanvas.Brush.Color := clWebBlanchedAlmond; - TargetCanvas.Pen.Color := TargetCanvas.Brush.Color; - TargetCanvas.Rectangle(CellRect); - end; - end; -end; - - procedure TMainForm.ListVariablesDblClick(Sender: TObject); begin menuEditVariable.Click; @@ -9180,6 +9551,8 @@ procedure TMainForm.HostListGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtu if Sender = ListProcesses then begin Idx := Sender.GetNodeData(Node); Results := GridResult(Sender); + if not Results.Connection.Active then + Exit; Results.RecNo := Idx^; case Kind of ikNormal, ikSelected: begin @@ -9224,11 +9597,15 @@ procedure TMainForm.HostListGetText(Sender: TBaseVirtualTree; Node: PVirtualNode begin Idx := Sender.GetNodeData(Node); Results := GridResult(Sender); - // See issue #3416 - if (Results = nil) and (Sender <> ListVariables) then - Exit; - if Results <> nil then + + // See issue #3416. Note: ListVariables does not depend on a live result, but on a StringList. + if Sender <> ListVariables then begin + // See issue #1875 + if (Results = nil) or (not Results.Connection.Active) then + Exit; Results.RecNo := Idx^; + end; + if (Sender = ListStatus) and (Column in [1,2,3]) then begin CellText := Results.Col(1); @@ -9314,12 +9691,16 @@ procedure TMainForm.HostListGetText(Sender: TBaseVirtualTree; Node: PVirtualNode procedure TMainForm.menuEditVariableClick(Sender: TObject); var Dialog: TfrmEditVariable; + VarValue: String; begin Dialog := TfrmEditVariable.Create(Self); try try Dialog.VarName := ListVariables.Text[ListVariables.FocusedNode, 0]; - Dialog.VarValue := ListVariables.Text[ListVariables.FocusedNode, 1]; + VarValue := ListVariables.Text[ListVariables.FocusedNode, 1]; + if VarValue = TEXT_NULL then + VarValue := ''; + Dialog.VarValue := VarValue; // Refresh list node if Dialog.ShowModal = mrOK then InvalidateVT(ListVariables, VTREE_NOTLOADED, False); @@ -9352,65 +9733,72 @@ procedure TMainForm.DBtreeGetText(Sender: TBaseVirtualTree; Node: Bytes: Int64; AllListsCached: Boolean; begin - DBObj := Sender.GetNodeData(Node); - case Column of - 0: case DBObj.NodeType of - lntNone: CellText := DBObj.Connection.Parameters.SessionPath; - lntDb: CellText := DBObj.Database; - lntGroup: begin - CellText := DBObj.Name; - if Sender.ChildrenInitialized[Node] then - CellText := CellText + ' (' + FormatNumber(Sender.ChildCount[Node]) + ')'; - end; - lntTable..lntEvent: try - if (DBObj.Schema <> '') and (DBObj.Connection.Parameters.NetTypeGroup = ngMSSQL) then - CellText := DBObj.Schema + '.' + DBObj.Name - else + try + DBObj := Sender.GetNodeData(Node); + case Column of + 0: case DBObj.NodeType of + lntNone: CellText := DBObj.Connection.Parameters.SessionPath; + lntDb: CellText := DBObj.Database; + lntGroup: begin CellText := DBObj.Name; - except - CellText := DBObj.Name; + if Sender.ChildrenInitialized[Node] then + CellText := CellText + ' (' + FormatNumber(Sender.ChildCount[Node]) + ')'; + end; + lntTable..lntEvent: try + if (DBObj.Schema <> '') and (DBObj.Connection.Parameters.NetTypeGroup = ngMSSQL) then + CellText := DBObj.Schema + '.' + DBObj.Name + else + CellText := DBObj.Name; + except + CellText := DBObj.Name; + end; + lntColumn: CellText := DBObj.Column; end; - lntColumn: CellText := DBObj.Column; - end; - 1: if DBObj.Connection.Active then case DBObj.NodeType of - // Calculate and display the sum of all table sizes in ALL dbs if all table lists are cached - lntNone: begin - AllListsCached := true; - for i:=0 to DBObj.Connection.AllDatabases.Count-1 do begin - if not DBObj.Connection.DbObjectsCached(DBObj.Connection.AllDatabases[i]) then begin - AllListsCached := false; - break; - end; - end; - // Will be also set to a negative value by GetTableSize and results of SHOW TABLES - Bytes := -1; - if AllListsCached then begin - Bytes := 0; + 1: if DBObj.Connection.Active then case DBObj.NodeType of + // Calculate and display the sum of all table sizes in ALL dbs if all table lists are cached + lntNone: begin + AllListsCached := true; for i:=0 to DBObj.Connection.AllDatabases.Count-1 do begin - DBObjects := DBObj.Connection.GetDBObjects(DBObj.Connection.AllDatabases[i]); - Inc(Bytes, DBObjects.DataSize); + if not DBObj.Connection.DbObjectsCached(DBObj.Connection.AllDatabases[i]) then begin + AllListsCached := false; + break; + end; + end; + // Will be also set to a negative value by GetTableSize and results of SHOW TABLES + Bytes := -1; + if AllListsCached then begin + Bytes := 0; + for i:=0 to DBObj.Connection.AllDatabases.Count-1 do begin + DBObjects := DBObj.Connection.GetDBObjects(DBObj.Connection.AllDatabases[i]); + Inc(Bytes, DBObjects.DataSize); + end; end; + if Bytes >= 0 then CellText := FormatByteNumber(Bytes) + else CellText := ''; end; - if Bytes >= 0 then CellText := FormatByteNumber(Bytes) - else CellText := ''; - end; - // Calculate and display the sum of all table sizes in ONE db, if the list is already cached. - lntDb: begin - if not DBObj.Connection.DbObjectsCached(DBObj.Database) then - CellText := '' - else begin - DBObjects := DBObj.Connection.GetDBObjects(DBObj.Database); - CellText := FormatByteNumber(DBObjects.DataSize); + // Calculate and display the sum of all table sizes in ONE db, if the list is already cached. + lntDb: begin + if not DBObj.Connection.DbObjectsCached(DBObj.Database) then + CellText := '' + else begin + DBObjects := DBObj.Connection.GetDBObjects(DBObj.Database); + CellText := FormatByteNumber(DBObjects.DataSize); + end; end; - end; - lntTable: begin - if DBObj.Size >= 0 then - CellText := FormatByteNumber(DBObj.Size) - else - CellText := ''; - end - else CellText := ''; // Applies for views/procs/... which have no size - end; + lntTable: begin + if DBObj.Size >= 0 then + CellText := FormatByteNumber(DBObj.Size) + else + CellText := ''; + end + else CellText := ''; // Applies for views/procs/... which have no size + end; + end; + except + // Uploaded crash reports show multiple different situations with an AV, + // some of them in conjunction with a killed query. + on E:EAccessViolation do + CellText := ''; end; end; @@ -9427,6 +9815,8 @@ procedure TMainForm.DBtreeGetImageIndex(Sender: TBaseVirtualTree; Node: if Column > 0 then Exit; DBObj := Sender.GetNodeData(Node); + if not Assigned(DBObj) then + Exit; case Kind of ikNormal, ikSelected: begin ImageIndex := DBObj.ImageIndex; @@ -9573,7 +9963,6 @@ procedure TMainForm.DBtreeFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualN var DBObj, PrevDBObj, ParentDBObj: PDBObject; MainTabToActivate: TTabSheet; - TabHostName: String; EnteringSession: Boolean; begin // Set wanted main tab and call SetMainTab later, when all lists have been invalidated @@ -9631,6 +10020,7 @@ procedure TMainForm.DBtreeFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualN SelectedTableForeignKeys.Clear; AppSettings.SessionPath := GetRegKeyTable; SelectedTableTimestampColumns.Text := AppSettings.ReadString(asTimestampColumns); + menuQueryExactRowCount.Checked := False; InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); try if FActiveDbObj.NodeType in [lntTable, lntView] then begin @@ -9710,12 +10100,8 @@ procedure TMainForm.DBtreeFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualN InvalidateVT(ListTables, VTREE_NOTLOADED, True); if FActiveDbObj.NodeType = lntGroup then InvalidateVT(ListTables, VTREE_NOTLOADED, True); - if FActiveDbObj.Connection.Parameters.IsAnySQLite then // Prefer visible filename over visible left part of path - TabHostName := StrEllipsis(FActiveDbObj.Connection.Parameters.HostName, 60, False) - else - TabHostName := FActiveDbObj.Connection.Parameters.HostName; - SetTabCaption(tabHost.PageIndex, _('Host')+': '+TabHostName); + SetTabCaption(tabHost.PageIndex, FActiveDbObj.Connection.Parameters.SessionName); SetTabCaption(tabDatabase.PageIndex, _('Database')+': '+FActiveDbObj.Connection.Database); ShowStatusMsg(FActiveDbObj.Connection.Parameters.NetTypeName(False)+' '+FActiveDbObj.Connection.ServerVersionStr, 3); end else begin @@ -10000,9 +10386,9 @@ procedure TMainForm.RefreshTree(FocusNewObject: TDBObject=nil); if not Assigned(DBtree.FocusedNode) then SetActiveDatabase('', FocusNewObject.Connection); except + on E:Exception do + LogSQL('RefreshTree: '+E.Message, lcInfo); end; - if not Assigned(DBtree.FocusedNode) then - raise Exception.Create(_('Could not find node to focus.')); finally FTreeRefreshInProgress := False; @@ -10084,6 +10470,29 @@ procedure TMainForm.menuToggleAllClick(Sender: TObject); end; +procedure TMainForm.menuCopyColumnNamesClick(Sender: TObject); +var + Grid: TVirtualStringTree; + Col: TColumnIndex; + List: TStringList; +begin + if Sender is TMenuItem then + Grid := PopupComponent(Sender) as TVirtualStringTree + else if Screen.ActiveControl is TVirtualStringTree then + Grid := Screen.ActiveControl as TVirtualStringTree + else + Exit; + + List := TStringList.Create; + Col := Grid.Header.Columns.GetFirstVisibleColumn(True); + while Col > NoColumn do begin + List.Add(Grid.Header.Columns[Col].Text); + Col := Grid.Header.Columns.GetNextVisibleColumn(Col); + end; + Clipboard.TryAsText := List.Text; + List.Free; +end; + procedure TMainForm.menuTreeCollapseAllClick(Sender: TObject); var n: PVirtualNode; @@ -10118,7 +10527,7 @@ procedure TMainForm.editFilterSearchChange(Sender: TObject); for i:=0 to SelectedTableColumns.Count-1 do begin // The normal case: do a LIKE comparison Condition := '''%' + Conn.EscapeString(ed.Text, True, False)+'%'''; - Condition := Conn.GetSQLSpecifity(spLikeCompare, [SelectedTableColumns[i].CastAsText, Condition]); + Condition := Conn.SqlProvider.GetSql(qLikeCompare, [SelectedTableColumns[i].CastAsText, Condition]); if not SelectedTableColumns[i].DataType.ValueMustMatch.IsEmpty then begin // Use an exact comparison for some PostgreSQL data types to overcome SQL errors, e.g. UUID, INT etc. // Also, prevent other errors by matching the value against a certain regular expression. @@ -10224,17 +10633,22 @@ procedure TMainForm.AnyGridGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; EditingAndFocused, IsScientific: Boolean; RowNumber: PInt64; Results: TDBQuery; - Timestamp: Int64; + TimestampInt: Int64; + TimestampFloat: Extended; + FloatFrac: String; DotPos, i, NumZeros, NumDecimals, KeepDecimals: Integer; ResultCol: Integer; begin if Column = -1 then Exit; + if TextType <> ttNormal then + Exit; ResultCol := Column - 1; if ResultCol < 0 then begin CellText := (Node.Index +1).ToString; Exit; end; + EditingAndFocused := Sender.IsEditing and (Node = Sender.FocusedNode) and (Column = Sender.FocusedColumn); Results := GridResult(Sender); if (Results = nil) or (not Results.Connection.Active) then begin @@ -10244,10 +10658,13 @@ procedure TMainForm.AnyGridGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; // Happens in some crashes, see issue #2462 if ResultCol >= Results.ColumnCount then Exit; + RowNumber := Sender.GetNodeData(Node); Results.RecNo := RowNumber^; - if Results.IsNull(ResultCol) and (not EditingAndFocused) then - CellText := TEXT_NULL + if Results.IsNull(ResultCol) then begin + // Grid editors come here through Tree.GetTextInfo(), provide empty string then + CellText := IfThen(EditingAndFocused, '', TEXT_NULL); + end else begin case Results.DataType(ResultCol).Category of dtcInteger, dtcReal: begin @@ -10258,9 +10675,16 @@ procedure TMainForm.AnyGridGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; CellText := Results.Col(ResultCol); end else if HandleUnixTimestampColumn(Sender, Column) then begin try - Timestamp := Trunc(StrToFloat(Results.Col(ResultCol), FFormatSettings)); - Dec(Timestamp, FTimeZoneOffset); - CellText := DateTimeToStr(UnixToDateTime(Timestamp)); + TimestampFloat := StrToFloat(Results.Col(ResultCol), FFormatSettings); + TimestampInt := Trunc(TimestampFloat); + Dec(TimestampInt, FTimeZoneOffset); + CellText := DateTimeToStr(UnixToDateTime(TimestampInt)); + + FloatFrac := Results.Col(ResultCol); + if FloatFrac.Contains(FFormatSettings.DecimalSeparator) then begin + FloatFrac := FloatFrac.Substring(Pos(FFormatSettings.DecimalSeparator, FloatFrac)); + CellText := CellText + '.' + FloatFrac; + end; except // EConvertError in StrToFloat or EInvalidOp in Trunc or... on E:Exception do begin @@ -10349,6 +10773,8 @@ procedure TMainForm.AnyGridPaintText(Sender: TBaseVirtualTree; const TargetCanva end; r := GridResult(Sender); + if not Assigned(r) then + Exit; RowNumber := Sender.GetNodeData(Node); r.RecNo := RowNumber^; @@ -10407,6 +10833,8 @@ procedure TMainForm.DataGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHit // Header click disabled if not AppSettings.ReadBool(asColumnHeaderClick) then Exit; + if HitInfo.Column = NoColumn then + Exit; ColName := Sender.Columns[HitInfo.Column].Text; // Add a new order column after a columns title has been clicked // Check if order column is already existant @@ -10425,6 +10853,7 @@ procedure TMainForm.DataGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHit else SortOrder := sioAscending; FDataGridSortItems.AddNew(ColName, SortOrder); + LogSQL('Created sorting for column '+ColName+'/'+Integer(SortOrder).ToString+' in TMainForm.DataGridHeaderClick', lcDebug); end; // Refresh grid, and remember X scroll offset, so the just clicked column is still at the same place. @@ -10514,6 +10943,7 @@ procedure TMainForm.AnyGridNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Results: TDBQuery; RowNum: PInt64; Timestamp: Int64; + TimestampFraction, StrWithoutMs: String; IsNull: Boolean; ResultCol: Integer; begin @@ -10526,19 +10956,21 @@ procedure TMainForm.AnyGridNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; try if (not FGridEditFunctionMode) and (Results.DataType(ResultCol).Category in [dtcInteger, dtcReal]) then begin if HandleUnixTimestampColumn(Sender, Column) then begin - Timestamp := DateTimeToUnix(StrToDateTime(NewText)); + TimestampFraction := RegExprGetMatch('(\.\d+)$', NewText, 1); + StrWithoutMs := ReplaceRegExpr('\.\d+$', NewText, ''); + Timestamp := DateTimeToUnix(StrToDateTime(StrWithoutMs)); Inc(Timestamp, FTimeZoneOffset); - NewText := IntToStr(Timestamp) + NewText := IntToStr(Timestamp) + TimestampFraction; end else NewText := NewText; end; + FClipboardHasNull := FClipboardHasNull and (Clipboard.TryAsText = ''); IsNull := FGridPasting and FClipboardHasNull; Results.SetCol(ResultCol, NewText, IsNull, FGridEditFunctionMode); except - on E:EDbError do + on E:Exception do ErrorDialog(E.Message); end; - FGridEditFunctionMode := False; ValidateControls(Sender); end; @@ -10585,6 +11017,9 @@ procedure TMainForm.AnyGridFocusChanged(Sender: TBaseVirtualTree; Node: PVirtual Sender.ScrollIntoView(Sender.FocusedNode, False, True); // Required for highlighting fields with same text Sender.Invalidate; + // Reset flags when moving focus + FGridEditFunctionMode := False; + FDataEditWithoutLookup := False; end; @@ -10619,15 +11054,17 @@ procedure TMainForm.AnyGridKeyDown(Sender: TObject; var Key: Word; Shift: TShift Key := 0; end; end; - VK_RETURN: if Assigned(g.FocusedNode) then g.EditNode(g.FocusedNode, g.FocusedColumn); - VK_DOWN: if g.FocusedNode = g.GetLast then actDataInsertExecute(actDataInsert); - VK_NEXT: if (g = DataGrid) and (g.FocusedNode = g.GetLast) then actDataShowNext.Execute; + VK_RETURN: if Assigned(g.FocusedNode) and (Shift=[]) then g.EditNode(g.FocusedNode, g.FocusedColumn); + VK_DOWN: if (g.FocusedNode = g.GetLast) and (Shift=[]) then actDataInsertExecute(actDataInsert); + VK_NEXT: if (g = DataGrid) and (g.FocusedNode = g.GetLast) and (Shift=[]) then actDataShowNext.Execute; end; end; procedure TMainForm.AnyGridEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); +var + ColInfo: TTableColumn; begin Allowed := False; try @@ -10635,6 +11072,15 @@ procedure TMainForm.AnyGridEditing(Sender: TBaseVirtualTree; Node: ErrorDialog(_('Could not load full row data.')) else begin Allowed := True; + if Sender = DataGrid then begin + ColInfo := SelectedTableColumns.FindByName(Sender.Header.Columns[Column].Text); + if Assigned(ColInfo) then begin + Allowed := ColInfo.GenerationExpression.IsEmpty; + if not Allowed then + ErrorDialog(f_('Column %s is defined as generated per "%s". You cannot edit its content.', [Column.ToString, ColInfo.GenerationExpression])); + end; + end; + // Move Esc shortcut from "Cancel row editing" to "Cancel cell editing" actDataCancelChanges.ShortCut := 0; actDataPostChanges.ShortCut := 0; @@ -10688,6 +11134,7 @@ procedure TMainForm.AnyGridCreateEditor(Sender: TBaseVirtualTree; Node: ResultCol: Integer; begin VT := Sender as TVirtualStringTree; + EditLink := nil; Results := GridResult(VT); RowNum := VT.GetNodeData(Node); Results.RecNo := RowNum^; @@ -10698,7 +11145,7 @@ procedure TMainForm.AnyGridCreateEditor(Sender: TBaseVirtualTree; Node: TblColumn := Results.ColAttributes(ResultCol); // Find foreign key values - if AppSettings.ReadBool(asForeignDropDown) and (Sender = DataGrid) then begin + if AppSettings.ReadBool(asForeignDropDown) and (Sender = DataGrid) and (not FDataEditWithoutLookup) then begin for ForeignKey in SelectedTableForeignKeys do begin idx := ForeignKey.Columns.IndexOf(DataGrid.Header.Columns[Column].Text); if idx > -1 then try @@ -10718,7 +11165,7 @@ procedure TMainForm.AnyGridCreateEditor(Sender: TBaseVirtualTree; Node: KeyCol := Conn.QuoteIdent(ForeignKey.ForeignColumns[idx]); if TextCol <> '' then begin - SQL := KeyCol+', ' + Conn.GetSQLSpecifity(spFuncLeft, [Conn.QuoteIdent(TextCol), 256])+ + SQL := KeyCol+', ' + Conn.SqlProvider.GetSql(qFuncLeft, [Conn.QuoteIdent(TextCol), 256])+ ' FROM ' + RefObj.QuotedDbAndTableName + ' GROUP BY '+KeyCol+', '+Conn.QuoteIdent(TextCol)+ // MSSQL complains if the text columns is not grouped ' ORDER BY '+Conn.QuoteIdent(TextCol); @@ -10742,9 +11189,9 @@ procedure TMainForm.AnyGridCreateEditor(Sender: TBaseVirtualTree; Node: EnumEditor.ValueList.Add(ForeignResults.Col(0)); if TextCol <> '' then begin if DisplayHex then - EnumEditor.DisplayList.Add(ForeignResults.HexValue(0)+': '+ForeignResults.Col(1)) + EnumEditor.DisplayList.Add(ForeignResults.Col(1) + ' (' + ForeignResults.HexValue(0) + ')') else - EnumEditor.DisplayList.Add(ForeignResults.Col(0)+': '+ForeignResults.Col(1)); + EnumEditor.DisplayList.Add(ForeignResults.Col(1) + ' (' + ForeignResults.Col(0) + ')'); end; ForeignResults.Next; end; @@ -10830,20 +11277,44 @@ procedure TMainForm.AnyGridCreateEditor(Sender: TBaseVirtualTree; Node: end; -procedure TMainForm.menuShowSizeColumnClick(Sender: TObject); +procedure TMainForm.actDisplayLogPanelExecute(Sender: TObject); +begin + if actDisplayLogPanel.Checked then begin + SynMemoSQLLog.Visible := True; + spltTopBottom.Visible := True; + // ensure z-order: top panel, splitter, memo + spltTopBottom.BringToFront; + SynMemoSQLLog.BringToFront; + end + else begin + spltTopBottom.Visible := False; + SynMemoSQLLog.Visible := False; + end; + AppSettings.ResetPath; + AppSettings.WriteBool(asDisplayLogPanel, actDisplayLogPanel.Checked); +end; + +procedure TMainForm.actDisplayObjectSizeExecute(Sender: TObject); var - Item: TMenuItem; + ColOptions: TVTColumnOptions; begin - if coVisible in DBtree.Header.Columns[1].Options then - DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options - [coVisible] + ColOptions := DBtree.Header.Columns[1].Options; + if actDisplayObjectSize.Checked then + ColOptions := ColOptions + [coVisible] else - DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options + [coVisible]; - Item := Sender as TMenuItem; - Item.Checked := coVisible in DBtree.Header.Columns[1].Options; + ColOptions := ColOptions - [coVisible]; + DBtree.Header.Columns[1].Options := ColOptions; AppSettings.ResetPath; - AppSettings.WriteBool(asDisplayObjectSizeColumn, Item.Checked); + AppSettings.WriteBool(asDisplayObjectSizeColumn, actDisplayObjectSize.Checked); end; +procedure TMainForm.actDisplayTreeFiltersExecute(Sender: TObject); +begin + ToolBarTree.Visible := actDisplayTreeFilters.Checked; + pnlLeftResize(Sender); // Updates width of filter boxes + AppSettings.ResetPath; + AppSettings.WriteBool(asDisplayTreeFilters, actDisplayTreeFilters.Checked); +end; procedure TMainForm.menuAlwaysGenerateFilterClick(Sender: TObject); begin @@ -10889,7 +11360,10 @@ procedure TMainForm.AutoCalcColWidth(Tree: TVirtualStringTree; Column: TColumnIn if not (coVisible in Col.Options) then Exit; ColTextWidth := Tree.Canvas.TextWidth(Col.Text); - // Add space for sort glyph + // Add space for column id ... + if (Column > 0) and AppSettings.ReadBool(asShowRowId) then + ColTextWidth := ColTextWidth + Tree.Canvas.TextWidth(Column.ToString) + 5; + // ... and sort glyph if Col.ImageIndex > -1 then ColTextWidth := ColTextWidth + 20; Node := Tree.GetFirstVisible; @@ -10910,8 +11384,8 @@ procedure TMainForm.AutoCalcColWidth(Tree: TVirtualStringTree; Column: TColumnIn // here if the query or connection dies. Rect := Tree.GetDisplayRect(Node, Column, True, True); ContentTextWidth := Rect.Right - Rect.Left; - if vsMultiLine in Node.States then - ContentTextWidth := Max(ContentTextWidth, Tree.Canvas.TextWidth(Tree.Text[Node, Column])); + //if vsMultiLine in Node.States then + // ContentTextWidth := Max(ContentTextWidth, Tree.Canvas.TextWidth(Tree.Text[Node, Column])); ColTextWidth := Max(ColTextWidth, ContentTextWidth); inc(i); if i > 100 then break; @@ -10932,12 +11406,13 @@ procedure TMainForm.AnyGridBeforeCellPaint(Sender: TBaseVirtualTree; CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); var r: TDBQuery; - cl, clNull, clEven, clOdd, clSameData: TColor; + cl, clNull, clSameData: TColor; RowNumber: PInt64; - isEven, FocusedIsNull, CurrentIsNull: Boolean; + FocusedIsNull, CurrentIsNull: Boolean; FieldText, FocusedFieldText: String; VT: TVirtualStringTree; ResultCol: Integer; + SelectedNode: PVirtualNode; begin if Column = -1 then Exit; @@ -10963,15 +11438,7 @@ procedure TMainForm.AnyGridBeforeCellPaint(Sender: TBaseVirtualTree; RowNumber := Sender.GetNodeData(Node); r.RecNo := RowNumber^; - clEven := AppSettings.ReadInt(asRowBackgroundEven); - clOdd := AppSettings.ReadInt(asRowBackgroundOdd); - isEven := Node.Index mod 2 = 0; - if IsEven and (clEven <> clNone) then - cl := AppSettings.ReadInt(asRowBackgroundEven) - else if (not IsEven) and (clOdd <> clNone) then - cl := AppSettings.ReadInt(asRowBackgroundOdd) - else - cl := clNone; + cl := GetAlternatingRowBackground(Node); if (vsSelected in Node.States) and (Node = Sender.FocusedNode) and (Column = Sender.FocusedColumn) then begin // Focused cell @@ -10994,22 +11461,29 @@ procedure TMainForm.AnyGridBeforeCellPaint(Sender: TBaseVirtualTree; end; // Probably display background color on fields with same text - // Result pointer gets moved to the focused node.. careful! - if (Sender.FocusedNode <> nil) and (Sender.FocusedColumn > 0) then begin - if ((Node <> Sender.FocusedNode) and (Column = Sender.FocusedColumn)) - or ((Node = Sender.FocusedNode) and (Column <> Sender.FocusedColumn)) then begin + // Result pointer gets moved to selected nodes.. careful! + if (Sender.FocusedNode <> nil) and (Sender.FocusedColumn > 0) and (Sender.SelectedCount <= 100) then begin + if ((not Sender.Selected[Node]) and (Column = Sender.FocusedColumn)) + or (Sender.Selected[Node] and (Column <> Sender.FocusedColumn)) then begin clSameData := AppSettings.ReadInt(asHightlightSameTextBackground); if clSameData <> clNone then begin FieldText := r.Col(ResultCol); CurrentIsNull := r.IsNull(ResultCol); - RowNumber := Sender.GetNodeData(Sender.FocusedNode); - r.RecNo := RowNumber^; // moving result cursor - FocusedFieldText := r.Col(Sender.FocusedColumn-1); - FocusedIsNull := r.IsNull(Sender.FocusedColumn-1); - if (CompareText(FieldText, FocusedFieldText) = 0) and (CurrentIsNull = FocusedIsNull) then begin - TargetCanvas.Brush.Color := clSameData; - TargetCanvas.FillRect(CellRect); + + SelectedNode := GetNextNode(VT, nil, True); + while Assigned(SelectedNode) do begin + RowNumber := Sender.GetNodeData(SelectedNode); + r.RecNo := RowNumber^; // moving result cursor + FocusedFieldText := r.Col(Sender.FocusedColumn-1); + FocusedIsNull := r.IsNull(Sender.FocusedColumn-1); + if (CompareText(FieldText, FocusedFieldText) = 0) and (CurrentIsNull = FocusedIsNull) then begin + TargetCanvas.Brush.Color := clSameData; + TargetCanvas.FillRect(CellRect); + Break; // No need to look further + end; + SelectedNode := GetNextNode(VT, SelectedNode, True); end; + end; end; end; @@ -11128,6 +11602,7 @@ procedure TMainForm.HandleDataGridAttributes(RefreshingData: Boolean); SortItem := FDataGridSortItems.AddNew; SortItem.Column := rx.Match[2]; SortItem.Order := TSortItemOrder(StrToIntDef(rx.Match[1], 0)); + LogSQL('Restored sorting for column '+SortItem.Column+'/'+Integer(SortItem.Order).ToString+' in TMainForm.HandleDataGridAttributes', lcDebug); Break; end; end; @@ -11160,7 +11635,10 @@ procedure TMainForm.AnyGridMouseUp(Sender: TObject; Button: TMouseButton; Grid := Sender as TVirtualStringTree; if not Assigned(Grid.FocusedNode) then Exit; - Grid.GetHitTestInfoAt(X, Y, False, Hit); + // Exit early for non-result-grids like ListTables + if Grid <> ActiveGrid then + Exit; + Grid.GetHitTestInfoAt(X, Y, True, Hit); if (Hit.HitNode = nil) or (Hit.HitColumn = NoColumn) or (Hit.HitColumn = InvalidColumn) then begin Results := GridResult(Grid); if Results.Modified then begin @@ -11171,31 +11649,6 @@ procedure TMainForm.AnyGridMouseUp(Sender: TObject; Button: TMouseButton; end; -procedure TMainForm.ListDatabasesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; - Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; - var ContentRect: TRect); -var - vt: TVirtualStringTree; - Val, Max: Extended; - LoopNode: PVirtualNode; -begin - // Display color bars - if Column in [1,2,4..9] then begin - vt := Sender as TVirtualStringTree; - // Find out maximum value in column - LoopNode := vt.GetFirst; - Max := 1; - while Assigned(LoopNode) do begin - Val := MakeFloat(vt.Text[LoopNode, Column]); - if Val > Max then - Max := Val; - LoopNode := vt.GetNext(LoopNode); - end; - PaintColorBar(MakeFloat(vt.Text[Node, Column]), Max, TargetCanvas, CellRect); - end; -end; - - procedure TMainForm.ListDatabasesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); var vt: TVirtualStringTree; @@ -11403,23 +11856,24 @@ procedure TMainForm.HostListBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: FVariableNames.Sorted := True; FSessionVars := TStringList.Create; FGlobalVars := TStringList.Create; - Variables := Conn.GetResults(Conn.GetSQLSpecifity(spSessionVariables)); + Variables := Conn.GetResults(Conn.SqlProvider.GetSql(qSessionVariables)); while not Variables.Eof do begin FVariableNames.Add(Variables.Col(0)); - FSessionVars.Values[Variables.Col(0)] := Variables.Col(1); + FSessionVars.Values[Variables.Col(0)] := IfThen(Variables.IsNull(1), TEXT_NULL, Variables.Col(1)); Variables.Next; end; Variables.Free; - Variables := Conn.GetResults(Conn.GetSQLSpecifity(spGlobalVariables)); + Variables := Conn.GetResults(Conn.SqlProvider.GetSql(qGlobalVariables)); while not Variables.Eof do begin FVariableNames.Add(Variables.Col(0)); FGlobalVars.Values[Variables.Col(0)] := Variables.Col(1); + FGlobalVars.Values[Variables.Col(0)] := IfThen(Variables.IsNull(1), TEXT_NULL, Variables.Col(1)); Variables.Next; end; Variables.Free; vt.RootNodeCount := FVariableNames.Count; end else if vt = ListStatus then begin - Results := Conn.GetResults(Conn.GetSQLSpecifity(spGlobalStatus)); + Results := Conn.GetResults(Conn.SqlProvider.GetSql(qGlobalStatus)); FStatusServerUptime := Conn.ServerUptime; end else if vt = ListProcesses then begin case Conn.Parameters.NetTypeGroup of @@ -11473,8 +11927,8 @@ procedure TMainForm.HostListBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: ', RTRIM('+Conn.QuoteIdent('p')+'.'+Conn.QuoteIdent('status')+'), '+ 'NULL AS '+Conn.QuoteIdent('Info')+' '+ 'FROM '+Conn.QuoteIdent('sys')+'.'+Conn.QuoteIdent('sysprocesses')+' AS '+Conn.QuoteIdent('p')+ - ', '+Conn.GetSQLSpecifity(spDatabaseTable)+' AS '+Conn.QuoteIdent('d')+ - ' WHERE '+Conn.QuoteIdent('p')+'.'+Conn.QuoteIdent('dbid')+'='+Conn.QuoteIdent('d')+'.'+Conn.GetSQLSpecifity(spDatabaseTableId) + ', '+Conn.SqlProvider.GetSql(qDatabaseTable)+' AS '+Conn.QuoteIdent('d')+ + ' WHERE '+Conn.QuoteIdent('p')+'.'+Conn.QuoteIdent('dbid')+'='+Conn.QuoteIdent('d')+'.'+Conn.SqlProvider.GetSql(qDatabaseTableId) ); end; ngPgSQL: begin @@ -11500,7 +11954,7 @@ procedure TMainForm.HostListBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: Results.Next; end; end else if vt = ListCommandStats then begin - Results := Conn.GetResults(Conn.GetSQLSpecifity(spCommandsCounters)); + Results := Conn.GetResults(Conn.SqlProvider.GetSql(qCommandsCounters)); FCommandStatsServerUptime := Conn.ServerUptime; FCommandStatsQueryCount := 0; while not Results.Eof do begin @@ -11535,10 +11989,43 @@ procedure TMainForm.HostListBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanv var ContentRect: TRect); var vt: TVirtualStringTree; + Val, Max: Extended; + LoopNode: PVirtualNode; + SessionVal, GlobalVal: String; begin + PaintAlternatingRowBackground(TargetCanvas, Node, CellRect); vt := Sender as TVirtualStringTree; - if (Column = 5) and (vt = ListProcesses) then + + if (Column in [1,2,4..9]) and (vt = ListDatabases) then begin + // Find out maximum value in column + LoopNode := vt.GetFirst; + Max := 1; + while Assigned(LoopNode) do begin + Val := MakeFloat(vt.Text[LoopNode, Column]); + if Val > Max then + Max := Val; + LoopNode := vt.GetNext(LoopNode); + end; + PaintColorBar(MakeFloat(vt.Text[Node, Column]), Max, TargetCanvas, CellRect); + end; + + // Highlight cell if session variable is different to global variable + if (Column = 1) and (vt = ListVariables) then begin + SessionVal := vt.Text[Node, 1]; + GlobalVal := vt.Text[Node, 2]; + if SessionVal <> GlobalVal then begin + TargetCanvas.Brush.Color := clWebBlanchedAlmond; + TargetCanvas.Pen.Color := TargetCanvas.Brush.Color; + TargetCanvas.Rectangle(CellRect); + end; + end; + + // Nothing special on Status tab + + if (Column = 5) and (vt = ListProcesses) then begin PaintColorBar(MakeFloat(vt.Text[Node, Column]), FProcessListMaxTime, TargetCanvas, CellRect); + end; + if (Column = 4) and (vt = ListCommandStats) then begin // Only paint bar in percentage column PaintColorBar(MakeFloat(vt.Text[Node, Column]), 100, TargetCanvas, CellRect); @@ -11550,10 +12037,10 @@ procedure TMainForm.actFollowForeignKeyExecute(Sender: TObject); var Results: TDBQuery; RowNum: PInt64; - FocusedColumnName, ForeignColumnName, ReferenceTable: String; + FocusedColumnName, ForeignColumnName: String; ForeignKey: TForeignKey; i: Integer; - DBObj: TDBObject; + DBObj, ReferenceTable: TDBObject; Datatype: TDBDatatype; DbObjects: TDBObjectList; Filter: String; @@ -11564,13 +12051,14 @@ procedure TMainForm.actFollowForeignKeyExecute(Sender: TObject); Results.RecNo := RowNum^; FocusedColumnName := Results.ColumnOrgNames[DataGrid.FocusedColumn-1]; Conn := Results.Connection; + ReferenceTable := nil; // find foreign key for current column for ForeignKey in ActiveDBObj.TableForeignKeys do begin i := ForeignKey.Columns.IndexOf(FocusedColumnName); if i > -1 then begin ForeignColumnName := ForeignKey.ForeignColumns[i]; - ReferenceTable := ForeignKey.ReferenceTable; + ReferenceTable := ForeignKey.ReferenceTableObj; break; end; end; @@ -11578,6 +12066,10 @@ procedure TMainForm.actFollowForeignKeyExecute(Sender: TObject); LogSQL(f_('Foreign key not found for column "%s"', [FocusedColumnName]), lcInfo); Exit; end; + if ReferenceTable = nil then begin + LogSQL(_('Foreign key table not found')); + Exit; + end; Datatype := Results.DataType(DataGrid.FocusedColumn-1); // filter to show only rows linked by the foreign key if DataType.Category in [dtcBinary, dtcSpatial] then @@ -11586,13 +12078,7 @@ procedure TMainForm.actFollowForeignKeyExecute(Sender: TObject); Filter := Conn.QuoteIdent(ForeignColumnName)+'='+Conn.EscapeString(Results.Col(DataGrid.FocusedColumn-1)); // Jumping to ReferenceTable. Caution, this invalidates the above used Results - DbObjects := Conn.GetDBObjects(ActiveDatabase); - for DBObj in DbObjects do begin - if DBObj.Database + '.' + DBObj.Name = ReferenceTable then begin - ActiveDBObj := DBObj; - Break; - end; - end; + ActiveDBObj := ReferenceTable; SynMemoFilter.Text := Filter; ToggleFilterPanel(True); @@ -11603,6 +12089,98 @@ procedure TMainForm.actFollowForeignKeyExecute(Sender: TObject); end; +procedure TMainForm.actCopyGridNodesExecute(Sender: TObject); +var + SenderControl: TComponent; + SenderName: String; + Grid: TVirtualStringTree; + Header, Body, Line, Data: String; + Separator, Encloser, Terminator: String; + Node: PVirtualNode; + Col: TColumnIndex; + Indent, NodesCopied: Integer; + IsFirstCol: Boolean; +begin + // Copy tree nodes as CSV, from any VirtualTree, not only from data or result grids + // See issue #2083 + SenderControl := PopupComponent(Sender); + if SenderControl=nil then + SenderControl := Screen.ActiveControl; + + if not (SenderControl is TVirtualStringTree) then begin + if SenderControl=nil then + SenderName := 'nil' + else + SenderName := SenderControl.Name; + ErrorDialog(f_('No listing or tree focused. ActiveControl is %s', [SenderName])); + Exit; + end; + + Screen.Cursor := crHourGlass; + Grid := TVirtualStringTree(SenderControl); + // Grid.CopyToClipboard; // Does nothing (?) + + Separator := #9; + Encloser := ''; + Terminator := SLineBreak; + + Header := ''; + Col := Grid.Header.Columns.GetFirstVisibleColumn(True); + IsFirstCol := True; + while Col > NoColumn do begin + Data := Grid.Header.Columns[Col].Text; + //Data := StringReplace(Data, Encloser, Encloser+Encloser, [rfReplaceAll]); + if not IsFirstCol then + Header := Header + Separator; + IsFirstCol := False; + //Header := Header + Encloser + Data + Encloser; + Header := Header + Data; + Col := Grid.Header.Columns.GetNextVisibleColumn(Col); + end; + Header := Header + Terminator; + + Body := ''; + NodesCopied := 0; + Node := Grid.GetFirstInitialized; + while Assigned(Node) do begin + if Grid.IsVisible[Node] then begin + IsFirstCol := True; + Line := ''; + + // One empty cell for each indentation level + for Indent := 1 to Grid.GetNodeLevel(Node) do begin + if not IsFirstCol then + Line := Line + Separator; + IsFirstCol := False; + //Line := Line + Encloser + Encloser; + end; + + // Add data cells + Col := Grid.Header.Columns.GetFirstVisibleColumn(True); + while Col > NoColumn do begin + Data := Grid.Text[Node, Col]; + //Data := StringReplace(Data, Encloser, Encloser+Encloser, [rfReplaceAll]); + if not IsFirstCol then + Line := Line + Separator; + IsFirstCol := False; + //Line := Line + Encloser + Data + Encloser; + Line := Line + Data; + Col := Grid.Header.Columns.GetNextVisibleColumn(Col); + end; + + Body := Body + Line + Terminator; + Inc(NodesCopied); + end; + Node := Grid.GetNextInitialized(Node); + end; + + Clipboard.TryAsText := Header + Body; + + Screen.Cursor := crDefault; + LogSQL(f_('%s: %s lines copied to clipboard', [SLogPrefixInfo, FormatNumber(NodesCopied)]), lcInfo); + MessageBeep(MB_ICONASTERISK); +end; + procedure TMainForm.actCopyOrCutExecute(Sender: TObject); var CurrentControl: TWinControl; @@ -11617,7 +12195,6 @@ procedure TMainForm.actCopyOrCutExecute(Sender: TObject); ClpFormat: Word; ClpData: THandle; APalette: HPalette; - Exporter: TSynExporterRTF; Results: TDBQuery; RowNum: PInt64; ExportDialog: TfrmExportGrid; @@ -11686,17 +12263,10 @@ procedure TMainForm.actCopyOrCutExecute(Sender: TObject); end else if CurrentControl is TSynMemo then begin SynMemo := CurrentControl as TSynMemo; if SynMemo.SelAvail then begin - // Create both text and RTF clipboard format, so rich text applications can paste highlighted SQL - Clipboard.Open; - Clipboard.TryAsText := SynMemo.SelText; - Exporter := TSynExporterRTF.Create(Self); - Exporter.Highlighter := SynMemo.Highlighter; - Exporter.ExportAll(Explode(CRLF, SynMemo.SelText)); - if DoCut then SynMemo.CutToClipboard - else SynMemo.CopyToClipboard; - Exporter.CopyToClipboard; - Clipboard.Close; - Exporter.Free; + if DoCut then + SynMemo.CutToClipboard + else + SynMemo.CopyToClipboard; end; end else begin raise Exception.Create('Unhandled control in clipboard action: '+IfThen(Assigned(CurrentControl), CurrentControl.Name, 'nil')); @@ -11710,6 +12280,40 @@ procedure TMainForm.actCopyOrCutExecute(Sender: TObject); Screen.Cursor := crDefault; end; +procedure TMainForm.actCopyFormattedExecute(Sender: TObject); +var + Exporter: TSynExporterRTF; + SynMemo: TSynMemo; +begin + // Copy formatted SQL text to clipboard + if not (Screen.ActiveControl is TSynMemo) then begin + MessageBeep(MB_ICONWARNING); + LogSQL('Active control is not a SynMemo'); + Exit; + end; + + SynMemo := TSynMemo(Screen.ActiveControl); + if SynMemo.Highlighter = nil then begin + MessageBeep(MB_ICONWARNING); + LogSQL('No highlighter assigned to active SynMemo'); + Exit; + end; + + Exporter := TSynExporterRTF.Create(nil); + try + Exporter.Title := APPNAME; + Exporter.UseBackground := True; + Exporter.Highlighter := SynMemo.Highlighter; + Exporter.ExportAsText := False; + Clipboard.Open; + Clipboard.TryAsText := SynMemo.SelText; + Exporter.ExportAll(Explode(SLineBreak, SynMemo.SelText)); + Exporter.CopyToClipboard; + Clipboard.Close; + finally + Exporter.Free; + end; +end; procedure TMainForm.actPasteExecute(Sender: TObject); var @@ -11931,7 +12535,7 @@ procedure TMainForm.PlaceObjectEditor(Obj: TDBObject); SetupSynEditors(ActiveObjectEditor); end; ActiveObjectEditor.Init(Obj); - UpdateFilterPanel(Self); + buttonedEditClear(editFilterVT); end; end; @@ -12028,8 +12632,7 @@ procedure TMainForm.actNewQueryTabExecute(Sender: TObject); QueryTab.CloseButton.Height := 16; QueryTab.CloseButton.Flat := True; VirtualImageListMain.GetBitmap(134, QueryTab.CloseButton.Glyph); - QueryTab.CloseButton.OnMouseDown := CloseButtonOnMouseDown; - QueryTab.CloseButton.OnMouseUp := CloseButtonOnMouseUp; + QueryTab.CloseButton.OnClick := CloseButtonOnClick; SetTabCaption(QueryTab.TabSheet.PageIndex, ''); // Dumb code which replicates all controls from tabQuery @@ -12050,6 +12653,7 @@ procedure TMainForm.actNewQueryTabExecute(Sender: TObject); QueryTab.Memo.Parent := QueryTab.pnlMemo; QueryTab.Memo.Align := SynMemoQuery.Align; QueryTab.Memo.Constraints := SynMemoQuery.Constraints; + QueryTab.Memo.HintMode := SynMemoQuery.HintMode; QueryTab.Memo.Left := SynMemoQuery.Left; QueryTab.Memo.Options := SynMemoQuery.Options; QueryTab.Memo.PopupMenu := SynMemoQuery.PopupMenu; @@ -12069,6 +12673,7 @@ procedure TMainForm.actNewQueryTabExecute(Sender: TObject); QueryTab.Memo.OnMouseWheel := SynMemoQuery.OnMouseWheel; QueryTab.Memo.OnReplaceText := SynMemoQuery.OnReplaceText; QueryTab.Memo.OnPaintTransient := SynMemoQuery.OnPaintTransient; + QueryTab.Memo.OnTokenHint := SynMemoQuery.OnTokenHint; QueryTab.MemoLineBreaks := TLineBreaks(AppSettings.ReadInt(asLineBreakStyle)); SynCompletionProposal.AddEditor(QueryTab.Memo); @@ -12303,6 +12908,10 @@ procedure TMainForm.actResetPanelDimensionsExecute(Sender: TObject); if pnlPreview.Visible then begin pnlPreview.Height := AppSettings.GetDefaultInt(asDataPreviewHeight); end; + AppSettings.DeleteValue(asCompletionProposalWidth); + AppSettings.DeleteValue(asCompletionProposalNbLinesInWindow); + SynCompletionProposal.Width := AppSettings.ReadInt(asCompletionProposalWidth); + SynCompletionProposal.NbLinesInWindow := AppSettings.ReadInt(asCompletionProposalNbLinesInWindow); end; procedure TMainForm.menuRenameQueryTabClick(Sender: TObject); @@ -12336,6 +12945,7 @@ procedure TMainForm.popupMainTabsPopup(Sender: TObject); procedure TMainForm.CloseQueryTab(PageIndex: Integer); var NewPageIndex: Integer; + Grid: TVirtualStringTree; begin // Special case: the very first tab gets cleared but not closed if PageIndex = tabQuery.PageIndex then begin @@ -12344,6 +12954,10 @@ procedure TMainForm.CloseQueryTab(PageIndex: Integer); end; if not IsQueryTab(PageIndex, False) then Exit; + // Cancel cell editor if active, preventing crash. See issue #2040 + Grid := ActiveGrid; + if Assigned(Grid) and Grid.IsEditing then + Grid.CancelEditNode; // Ask user if query content shall be saved to disk if not ConfirmTabClose(PageIndex, False) then Exit; @@ -12367,9 +12981,8 @@ procedure TMainForm.CloseQueryTab(PageIndex: Integer); procedure TMainForm.actFavoriteObjectsOnlyExecute(Sender: TObject); begin // Click on "tree favorites" main button - AppSettings.ResetPath; - AppSettings.WriteBool(asFavoriteObjectsOnly, (Sender as TAction).Checked); editDatabaseTableFilterChange(Sender); + actFavoriteObjectsOnly.ImageIndex := IfThen(actFavoriteObjectsOnly.Checked, 112, 113); end; @@ -12435,6 +13048,8 @@ procedure TMainForm.editDatabaseTableFilterChange(Sender: TObject); Node := DBtree.GetNextInitialized(Node); end; DBtree.EndUpdate; + // Fix scroll height of the tree. See issue #2063 and #2002 + DBtree.Repaint; rxdb.Free; rxtable.Free; @@ -12562,23 +13177,38 @@ procedure TMainForm.editDatabaseTableFilterExit(Sender: TObject); end; -procedure TMainForm.CloseButtonOnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); +procedure TMainForm.CloseButtonOnClick(Sender: TObject); begin FLastMouseDownCloseButton := Sender; + TimerCloseTabByButton.Enabled := True; end; - -procedure TMainForm.CloseButtonOnMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); +procedure TMainForm.popupListHeaderPopup(Sender: TObject); +var + Item: TMenuItem; + i: Integer; +const + CustomItemTag = 123; begin - // Click on "Close" button of Query tab - if Button <> mbLeft then - Exit; - // Between MousDown and MouseUp it is possible that the focused tab has switched. As we simulate a mouse-click - // here, we must check if also the MouseDown event was fired on this particular button. See issue #1469. - if (Sender <> FLastMouseDownCloseButton) then - Exit; - // Prevent EAccessViolation in TControl.GetClientWidth, see issue #1640 - TimerCloseTabByButton.Enabled := True; + // Add a few items to the top of the grid's header context menu + for i:=popupListHeader.Items.Count-1 downto 0 do begin + Item := popupListHeader.Items[i]; + if Item.Tag = CustomItemTag then + Item.Free; + end; + + Item := TMenuItem.Create(popupListHeader); + Item.Tag := CustomItemTag; + Item.Caption := _('Toggle visibility of all columns'); + Item.OnClick := menuToggleAllClick; + popupListHeader.Items.Insert(0, Item); + + Item := TMenuItem.Create(popupListHeader); + Item.Tag := CustomItemTag; + Item.Caption := _('Copy column names'); + Item.OnClick := menuCopyColumnNamesClick; + Item.ImageIndex := actCopy.ImageIndex; + popupListHeader.Items.Insert(1, Item); end; @@ -12814,6 +13444,8 @@ procedure TMainForm.SetTabCaption(PageIndex: Integer; Text: String); // Some cases pass -1 which triggers a "List index out of bounds" in below cast if PageIndex = -1 then Exit; + // Escape hotkey accelerator in name of session, database or table + Text := EscapeHotkeyPrefix(Text); Text := StrEllipsis(Text, 70); // Special case if passed text is empty: Reset query tab caption to "Query #123" if (PageIndex = tabQuery.PageIndex) and (Text = '') then @@ -12860,51 +13492,53 @@ function TMainForm.ConfirmTabClear(PageIndex: Integer; AppIsClosing: Boolean): B begin Tab := QueryTabs[PageIndex-tabQuery.PageIndex]; - // Unhide tabsheet so the user sees the memo content. - // If the dialog is suppressed anyway, the user does not need to see the text, and we avoid - // storing this as the focused tab - if AppSettings.ReadBool(asPromptSaveFileOnTabClose) then begin - Tab.TabSheet.PageControl.ActivePage := Tab.TabSheet; - end; - - // Prompt for saving unsaved contents - if Tab.MemoFilename <> '' then - msg := f_('Save changes to file %s ?', [Tab.MemoFilename]) - else - msg := f_('Save content of tab "%s"?', [Trim(Tab.TabSheet.Caption)]); if AppSettings.RestoreTabsInitValue and AppIsClosing then begin - msg := msg + CRLF + CRLF + _('Your code is saved anyway, as auto-restoring is activated.'); - end; + Result := True; + end - if FConnections.Count > 0 then - MsgButtons := [mbYes, mbNo, mbCancel] - else - MsgButtons := [mbYes, mbNo]; + else begin + // Unhide tabsheet so the user sees the memo content. + // If the dialog is suppressed anyway, the user does not need to see the text, and we avoid + // storing this as the focused tab + if AppSettings.ReadBool(asPromptSaveFileOnTabClose) then begin + Tab.TabSheet.PageControl.ActivePage := Tab.TabSheet; + end; + // Prompt for saving unsaved contents + if Tab.MemoFilename <> '' then + msg := f_('Save changes to file %s ?', [Tab.MemoFilename]) + else + msg := f_('Save content of tab "%s"?', [Trim(Tab.TabSheet.Caption)]); - case MessageDialog(_('Modified query'), msg, mtConfirmation, MsgButtons, asPromptSaveFileOnTabClose) of - mrNo: Result := True; - mrYes: begin - if Tab.MemoFilename <> '' then begin - Tab.SaveContents(Tab.MemoFilename, False); - Result := True; - end - else begin - Dialog := TExtFileSaveDialog.Create(Self); - Dialog.Options := Dialog.Options + [fdoOverwritePrompt]; - Dialog.AddFileType('*.sql', _('SQL files')); - Dialog.AddFileType('*.*', _('All files')); - Dialog.DefaultExtension := 'sql'; - Dialog.LineBreakIndex := Tab.MemoLineBreaks; - if Dialog.Execute then begin - Tab.SaveContents(Dialog.FileName, False); - Tab.MemoLineBreaks := Dialog.LineBreakIndex; + if FConnections.Count > 0 then + MsgButtons := [mbYes, mbNo, mbCancel] + else + MsgButtons := [mbYes, mbNo]; + + case MessageDialog(_('Modified query'), msg, mtConfirmation, MsgButtons, asPromptSaveFileOnTabClose) of + mrNo: Result := True; + mrYes: begin + if Tab.MemoFilename <> '' then begin + Tab.SaveContents(Tab.MemoFilename, False); + Result := True; + end + else begin + Dialog := TExtFileSaveDialog.Create(Self); + Dialog.Options := Dialog.Options + [fdoOverwritePrompt]; + Dialog.AddFileType('*.sql', _('SQL files')); + Dialog.AddFileType('*.*', _('All files')); + Dialog.DefaultExtension := 'sql'; + Dialog.LineBreakIndex := Tab.MemoLineBreaks; + if Dialog.Execute then begin + Tab.SaveContents(Dialog.FileName, False); + Tab.MemoLineBreaks := Dialog.LineBreakIndex; + end; + // The save dialog can be cancelled. + Result := not Tab.Memo.Modified; + Dialog.Free; end; - // The save dialog can be cancelled. - Result := not Tab.Memo.Modified; - Dialog.Free; end; + else Result := False; end; - else Result := False; end; // Auto-backup logic @@ -13120,7 +13754,10 @@ procedure TMainForm.SetupSynEditor(Editor: TSynMemo); Editor.TabWidth := AppSettings.ReadInt(asTabWidth); Editor.MaxScrollWidth := BaseEditor.MaxScrollWidth; Editor.WantTabs := BaseEditor.WantTabs; + Editor.HintMode := BaseEditor.HintMode; Editor.OnKeyPress := BaseEditor.OnKeyPress; + Editor.OnMouseWheel := BaseEditor.OnMouseWheel; + Editor.OnTokenHint := BaseEditor.OnTokenHint; if Editor <> SynMemoSQLLog then begin Editor.OnPaintTransient := BaseEditor.OnPaintTransient; end; @@ -13135,6 +13772,7 @@ procedure TMainForm.actReformatSQLExecute(Sender: TObject); var m: TCustomSynEdit; CursorPosStart, CursorPosEnd: Integer; + Done: Boolean; begin // Reformat SQL query m := ActiveSynMemo(False); @@ -13151,7 +13789,14 @@ procedure TMainForm.actReformatSQLExecute(Sender: TObject); else begin frmReformatter := TfrmReformatter.Create(Self); frmReformatter.InputCode := m.SelText; - if frmReformatter.ShowModal = mrOk then begin + if AppSettings.ReadInt(asReformatterNoDialog) <> 0 then begin + frmReformatter.btnOkClick(Self); + Done := True; + end + else begin + Done := frmReformatter.ShowModal = mrOk; + end; + if Done then begin Screen.Cursor := crHourglass; m.UndoList.AddGroupBreak; m.SelText := frmReformatter.OutputCode; @@ -13187,7 +13832,8 @@ procedure TMainForm.menuQueryHelpersGenerateStatementClick(Sender: TObject); MenuItem: TMenuItem; sql, Val, WhereClause: String; i, idx: Integer; - ColumnNames, DefaultValues, KeyColumns: TStringList; + ColumnNames, DefaultValues: TStringList; + KeyColumns: TTableColumnList; Column: TTableColumn; Tree: TVirtualStringTree; Node: PVirtualNode; @@ -13223,34 +13869,34 @@ procedure TMainForm.menuQueryHelpersGenerateStatementClick(Sender: TObject); KeyColumns := ActiveConnection.GetKeyColumns(SelectedTableColumns, SelectedTableKeys); WhereClause := ''; for i:=0 to KeyColumns.Count-1 do begin - idx := ColumnNames.IndexOf(ActiveConnection.QuoteIdent(KeyColumns[i], False)); + idx := ColumnNames.IndexOf(ActiveConnection.QuoteIdent(KeyColumns[i].Name, False)); if idx > -1 then begin if WhereClause <> '' then WhereClause := WhereClause + ' AND '; - WhereClause := WhereClause + ActiveConnection.QuoteIdent(KeyColumns[i], False)+'='+DefaultValues[idx]; + WhereClause := WhereClause + ActiveConnection.QuoteIdent(KeyColumns[i].Name, False)+'='+DefaultValues[idx]; end; end; if MenuItem = menuQueryHelpersGenerateSelect then begin - sql := 'SELECT '+Implode(', ', ColumnNames)+CRLF+ - #9'FROM '+ActiveDbObj.QuotedName(False); + sql := 'SELECT ' + Implode(', ', ColumnNames) + SLineBreak + + CodeIndent + 'FROM '+ActiveDbObj.QuotedName(False); end else if MenuItem = menuQueryHelpersGenerateInsert then begin - sql := 'INSERT INTO '+ActiveDbObj.QuotedName(False)+CRLF+ - #9'('+Implode(', ', ColumnNames)+')'+CRLF+ - #9'VALUES ('+Implode(', ', DefaultValues)+')'; + sql := 'INSERT INTO ' + ActiveDbObj.QuotedName(False) + SLineBreak + + CodeIndent + '(' + Implode(', ', ColumnNames) + ')' + SLineBreak + + CodeIndent + 'VALUES (' + Implode(', ', DefaultValues) + ')'; end else if MenuItem = menuQueryHelpersGenerateUpdate then begin - sql := 'UPDATE '+ActiveDbObj.QuotedName(False)+CRLF+#9'SET'+CRLF; + sql := 'UPDATE ' + ActiveDbObj.QuotedName(False) + SLineBreak + CodeIndent + 'SET' + SLineBreak; if ColumnNames.Count > 0 then begin for i:=0 to ColumnNames.Count-1 do begin - sql := sql + #9#9 + ColumnNames[i] + '=' + DefaultValues[i] + ',' + CRLF; + sql := sql + CodeIndent(2) + ColumnNames[i] + '=' + DefaultValues[i] + ',' + SLineBreak; end; Delete(sql, Length(sql)-2, 1); end else - sql := sql + #9#9'??? # No column names selected!'+CRLF; - sql := sql + #9'WHERE ' + WhereClause; + sql := sql + CodeIndent(2) + '??? # No column names selected!' + SLineBreak; + sql := sql + CodeIndent + 'WHERE ' + WhereClause; end else if MenuItem = menuQueryHelpersGenerateDelete then begin sql := 'DELETE FROM '+ActiveDbObj.QuotedName(False)+' WHERE ' + WhereClause; @@ -13382,9 +14028,7 @@ procedure TMainForm.actDataResetSortingExecute(Sender: TObject); procedure TMainForm.WMCopyData(var Msg: TWMCopyData); var - i: Integer; Connection: TDBConnection; - Tab: TQueryTab; ConnectionParams: TConnectionParameters; FileNames: TStringList; RunFrom: String; @@ -13394,12 +14038,7 @@ procedure TMainForm.WMCopyData(var Msg: TWMCopyData); LogSQL(f_('Preventing second application instance - disabled in %s > %s > %s.', [_('Tools'), _('Preferences'), _('General')]), lcInfo); ConnectionParams := nil; ParseCommandLine(ParamBlobToStr(Msg.CopyDataStruct.lpData), ConnectionParams, FileNames, RunFrom); - if not RunQueryFiles(FileNames, nil, False) then begin - for i:=0 to FileNames.Count-1 do begin - Tab := GetOrCreateEmptyQueryTab(True); - Tab.LoadContents(FileNames[i], True, nil); - end; - end; + OpenQueryFiles(FileNames, nil, False); if ConnectionParams <> nil then InitConnection(ConnectionParams, True, Connection); end else @@ -13448,15 +14087,17 @@ procedure TMainForm.lblExplainProcessClick(Sender: TObject); var Tab: TQueryTab; UsedDatabase: String; + Conn: TDBConnection; begin // Click on "Explain" link label, in process viewer + Conn := ActiveConnection; actNewQueryTabExecute(Sender); Tab := QueryTabs[QueryTabs.Count-1]; UsedDatabase := listProcesses.Text[listProcesses.FocusedNode, 3]; if not UsedDatabase.IsEmpty then begin - Tab.Memo.Lines.Add('USE ' + ActiveConnection.QuoteIdent(UsedDatabase) + ';'); + Tab.Memo.Lines.Add(Conn.SqlProvider.GetSql(qUSEQuery, [Conn.QuoteIdent(UsedDatabase)]) + ';'); end; - Tab.Memo.Lines.Add('EXPLAIN' + sLineBreak + SynMemoProcessView.Text + ';'); + Tab.Memo.Lines.Add(Conn.SqlProvider.GetSql(qExplain, [SynMemoProcessView.Text]) + ';'); Tab.TabSheet.Show; actExecuteQueryExecute(Sender); end; @@ -13533,11 +14174,12 @@ procedure TMainForm.actCancelOperationExecute(Sender: TObject); Tab.ExecutionThread.Aborted := True; Killer := ActiveConnection.Parameters.CreateConnection(Self); Killer.Parameters := ActiveConnection.Parameters; + Killer.OwnsParameters := False; Killer.LogPrefix := _('Helper connection'); Killer.OnLog := LogSQL; try Killer.Active := True; - KillCommand := Killer.GetSQLSpecifity(spKillQuery, [ActiveConnection.ThreadId]); + KillCommand := Killer.SqlProvider.GetSql(qKillQuery, [ActiveConnection.ThreadId]); Killer.Query(KillCommand); except on E:EDbError do begin @@ -13580,8 +14222,9 @@ function TMainForm.GetEncodingByName(Name: String): TEncoding; 2: Result := TEncoding.GetEncoding(437); 3: Result := TEncoding.Unicode; 4: Result := TEncoding.BigEndianUnicode; - 5: Result := TEncoding.UTF8; + 5: Result := UTF8NoBOMEncoding; 6: Result := TEncoding.UTF7; + 7: Result := TEncoding.UTF8; end; end; @@ -13594,8 +14237,9 @@ function TMainForm.GetEncodingName(Encoding: TEncoding): String; else if (Encoding <> nil) and (Encoding.CodePage = 437) then idx := 2 else if Encoding = TEncoding.Unicode then idx := 3 else if Encoding = TEncoding.BigEndianUnicode then idx := 4 - else if Encoding = TEncoding.UTF8 then idx := 5 + else if Encoding = UTF8NoBOMEncoding then idx := 5 else if Encoding = TEncoding.UTF7 then idx := 6 + else if Encoding = TEncoding.UTF8 then idx := 7 else idx := 0; Result := FileEncodings[idx]; end; @@ -13662,10 +14306,12 @@ function TMainForm.GetCharsetByEncoding(Encoding: TEncoding): String; Result := 'utf16le' else if Encoding = TEncoding.BigEndianUnicode then Result := 'utf16' - else if Encoding = TEncoding.UTF8 then + else if Encoding = UTF8NoBOMEncoding then Result := 'utf8' else if Encoding = TEncoding.UTF7 then - Result := 'utf7'; + Result := 'utf7' + else if Encoding = TEncoding.UTF8 then + Result := 'utf8' // Auto-detection not supported here end; @@ -14160,10 +14806,12 @@ procedure TMainForm.treeQueryHelpersContextPopup(Sender: TObject; MousePos: TPoi menuQueryHelpersGenerateDelete.Enabled := False; menuInsertAtCursor.Enabled := False; menuLoadSnippet.Enabled := False; + menuRenameSnippet.Enabled := False; menuDeleteSnippet.Enabled := False; menuExplore.Enabled := False; menuHelp.Enabled := False; menuClearQueryHistory.Enabled := False; + menuClearQueryHistory.Caption := _('Clear query history ...'); case Tree.GetNodeLevel(Tree.FocusedNode) of 0: ; @@ -14186,6 +14834,7 @@ procedure TMainForm.treeQueryHelpersContextPopup(Sender: TObject; MousePos: TPoi menuInsertAtCursor.Enabled := True; end; TQueryTab.HelperNodeSnippets: begin + menuRenameSnippet.Enabled := True; menuDeleteSnippet.Enabled := True; menuInsertAtCursor.Enabled := True; menuLoadSnippet.Enabled := True; @@ -14200,6 +14849,7 @@ procedure TMainForm.treeQueryHelpersContextPopup(Sender: TObject; MousePos: TPoi 2: case Tree.FocusedNode.Parent.Parent.Index of TQueryTab.HelperNodeHistory: begin menuClearQueryHistory.Enabled := True; + menuClearQueryHistory.Caption := _('Delete this query from history'); menuInsertAtCursor.Enabled := True; end; end; @@ -14294,6 +14944,7 @@ procedure TMainForm.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean); on E:Exception do ErrorDialog(E.Message); end; + Done := True; end; // Sort list tables in idle time, so ListTables.TreeOptions.AutoSort does not crash the list @@ -14301,12 +14952,14 @@ procedure TMainForm.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean); if (PageControlMain.ActivePage = tabDatabase) and (not FListTablesSorted) then begin ListTables.SortTree(ListTables.Header.SortColumn, ListTables.Header.SortDirection); FListTablesSorted := True; + Done := True; end; // Re-enable refresh action when application is idle if (not actRefresh.Enabled) and (FRefreshActionDisabledAt < (GetTickCount - 1000)) then begin actRefresh.Enabled := True; + Done := True; end; end; @@ -14402,18 +15055,40 @@ procedure TMainForm.ApplicationDeActivate(Sender: TObject); procedure TMainForm.ApplicationShowHint(var HintStr: string; var CanShow: Boolean; var HintInfo: THintInfo); var - MainTabIndex, QueryTabIndex: integer; + MainTabIndex, QueryTabIndex, NewHideTimeout: integer; pt: TPoint; + Conn: TDBConnection; + Editor: TSynMemo; begin - // Show full filename in tab hint. See issue #3527 - // Code taken from http://www.delphipraxis.net/97988-tabsheet-hint-funktioniert-nicht.html if HintInfo.HintControl = PageControlMain then begin + // Show full filename in tab hint. See issue #3527 + // Code taken from http://www.delphipraxis.net/97988-tabsheet-hint-funktioniert-nicht.html pt := PageControlMain.ScreenToClient(Mouse.CursorPos); MainTabIndex := GetMainTabAt(pt.X, pt.Y); QueryTabIndex := MainTabIndex - tabQuery.PageIndex; - if (QueryTabIndex >= 0) and (QueryTabIndex < QueryTabs.Count) then + if (QueryTabIndex >= 0) and (QueryTabIndex < QueryTabs.Count) then begin HintStr := QueryTabs[QueryTabIndex].MemoFilename; + end + else if MainTabIndex = tabHost.TabIndex then begin + Conn := ActiveConnection; + HintStr := Conn.Parameters.Hostname; + if Conn.Parameters.IsAnySQLite then + HintStr := StringReplace(HintStr, DELIM, SLineBreak, [rfReplaceAll]); + end; HintInfo.ReshowTimeout := 1000; + SetHintFontByControl; + end + else if HintInfo.HintControl is TSynMemo then begin + // Token hint displaying through SynEdit's OnTokenHint event + Editor := TSynMemo(HintInfo.HintControl); + SetHintFontByControl(Editor); + NewHideTimeout := Min(Length(HintStr) * 100, 60*1000); + if NewHideTimeout > HintInfo.HideTimeout then + HintInfo.HideTimeout := NewHideTimeout; + end + else begin + // Probably reset hint font + SetHintFontByControl; end; end; @@ -14654,6 +15329,7 @@ constructor TQueryTab.Create; TimerStatusUpdate.Enabled := False; TimerStatusUpdate.Interval := 100; TimerStatusUpdate.OnTimer := TimerStatusUpdateOnTimer; + FFileEncoding := 'UTF-8'; end; @@ -14773,10 +15449,9 @@ function TQueryTab.LoadContents(Filepath: String; ReplaceContent: Boolean; Encod if Pos(AppSettings.DirnameSnippets, Filepath) = 0 then MainForm.AddOrRemoveFromQueryLoadHistory(Filepath, True, True); Memo.UndoList.AddGroupBreak; - Memo.BeginUpdate; LineBreaks := ScanLineBreaks(Content); if ReplaceContent then begin - Memo.SelectAll; + Memo.Clear; MemoLineBreaks := LineBreaks; end else begin if (MemoLineBreaks <> lbsNone) and (MemoLineBreaks <> LineBreaks) then @@ -14787,11 +15462,15 @@ function TQueryTab.LoadContents(Filepath: String; ReplaceContent: Boolean; Encod if MemoLineBreaks = lbsMixed then MessageDialog(_('This file contains mixed linebreaks. They have been converted to Windows linebreaks (CR+LF).'), mtInformation, [mbOK]); - Memo.SelText := Content; + if ReplaceContent then + Memo.Text := Content + else + Memo.SelText := Content; Memo.SelStart := Memo.SelEnd; - Memo.EndUpdate; Memo.Modified := False; MemoFilename := Filepath; + FileEncoding := MainForm.GetEncodingName(Encoding); + //showmessage(FileEncoding); Result := True; end; @@ -14816,7 +15495,7 @@ procedure TQueryTab.SaveContents(Filename: String; OnlySelection: Boolean); FileDir := ExtractFilePath(Filename); if not DirectoryExists(FileDir) then ForceDirectories(FileDir); - SaveUnicodeFile(Filename, Text); + SaveUnicodeFile(Filename, Text, MainForm.GetEncodingByName(FFileEncoding)); MemoFilename := Filename; Memo.Modified := False; LastSaveTime := GetTickCount; @@ -14880,7 +15559,7 @@ procedure TQueryTab.BackupUnsavedContent; if Memo.GetTextLen < SIZE_MB*10 then begin MainForm.LogSQL('Saving backup file to "'+MemoBackupFilename+'"...', lcDebug); MainForm.ShowStatusMsg(_('Saving backup file...')); - SaveUnicodeFile(MemoBackupFilename, Memo.Text); + SaveUnicodeFile(MemoBackupFilename, Memo.Text, UTF8NoBOMEncoding); end else begin MainForm.LogSQL('Unsaved tab contents too large (> 10M) for creating a backup.', lcDebug); end; @@ -15180,6 +15859,7 @@ constructor TResultTab.Create(AOwner: TQueryTab); Grid.OnPaintText := OrgGrid.OnPaintText; Grid.OnStartOperation := OrgGrid.OnStartOperation; FixVT(Grid, AppSettings.ReadInt(asGridRowLineCount)); + FTabIndex := QueryTab.ResultTabs.Count; // Will be 0 for the first one, even if we're already creating the first one here! end; destructor TResultTab.Destroy; diff --git a/source/preferences.dfm b/source/preferences.dfm index 942d76862..56b77b0ba 100644 --- a/source/preferences.dfm +++ b/source/preferences.dfm @@ -4,7 +4,7 @@ object frmPreferences: TfrmPreferences BorderIcons = [biSystemMenu] Caption = 'Preferences' ClientHeight = 482 - ClientWidth = 708 + ClientWidth = 712 Color = clBtnFace Constraints.MinHeight = 480 Constraints.MinWidth = 600 @@ -18,13 +18,13 @@ object frmPreferences: TfrmPreferences OnCreate = FormCreate OnShow = FormShow DesignSize = ( - 708 + 712 482) TextHeight = 14 object pagecontrolMain: TPageControl Left = 8 Top = 8 - Width = 692 + Width = 684 Height = 435 ActivePage = tabMisc Anchors = [akLeft, akTop, akRight, akBottom] @@ -37,7 +37,7 @@ object frmPreferences: TfrmPreferences ImageIndex = 137 ImageName = 'icons8-settings' DesignSize = ( - 684 + 676 406) object lblMySQLBinaries: TLabel Left = 8 @@ -68,7 +68,7 @@ object frmPreferences: TfrmPreferences Caption = 'GUI font: *' end object lblGUIFontSize: TLabel - Left = 648 + Left = 640 Top = 258 Width = 12 Height = 14 @@ -100,7 +100,7 @@ object frmPreferences: TfrmPreferences object chkAutoReconnect: TCheckBox Left = 220 Top = 31 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Automatically reconnect to previously opened sessions on startup' @@ -110,7 +110,7 @@ object frmPreferences: TfrmPreferences object chkRestoreLastDB: TCheckBox Left = 220 Top = 54 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Restore last used database on startup' @@ -154,7 +154,7 @@ object frmPreferences: TfrmPreferences object chkUpdateCheckBuilds: TCheckBox Left = 488 Top = 77 - Width = 191 + Width = 183 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Also check for updated nightly builds' @@ -165,7 +165,7 @@ object frmPreferences: TfrmPreferences object chkDoStatistics: TCheckBox Left = 220 Top = 100 - Width = 459 + Width = 451 Height = 17 Hint = 'This option, if enabled, will cause HeidiSQL to ping heidisql.co' + @@ -179,7 +179,7 @@ object frmPreferences: TfrmPreferences object chkAllowMultiInstances: TCheckBox Left = 220 Top = 8 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Allow multiple application instances' @@ -200,7 +200,7 @@ object frmPreferences: TfrmPreferences object editMySQLBinaries: TButtonedEdit Left = 220 Top = 174 - Width = 459 + Width = 451 Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain @@ -217,7 +217,7 @@ object frmPreferences: TfrmPreferences Tag = 1 Left = 220 Top = 228 - Width = 459 + Width = 451 Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] @@ -227,7 +227,7 @@ object frmPreferences: TfrmPreferences object editCustomSnippetsDirectory: TButtonedEdit Left = 220 Top = 201 - Width = 459 + Width = 451 Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain @@ -244,7 +244,7 @@ object frmPreferences: TfrmPreferences Tag = 1 Left = 220 Top = 255 - Width = 346 + Width = 338 Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] @@ -253,7 +253,7 @@ object frmPreferences: TfrmPreferences end object editGUIFontSize: TEdit Tag = 1 - Left = 573 + Left = 565 Top = 255 Width = 50 Height = 22 @@ -264,7 +264,7 @@ object frmPreferences: TfrmPreferences end object updownGUIFontSize: TUpDown Tag = 1 - Left = 623 + Left = 615 Top = 255 Width = 16 Height = 22 @@ -278,7 +278,7 @@ object frmPreferences: TfrmPreferences object chkWheelZoom: TCheckBox Left = 220 Top = 123 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Use Ctrl+Mousewheel for zooming' @@ -289,7 +289,7 @@ object frmPreferences: TfrmPreferences Tag = 1 Left = 220 Top = 282 - Width = 346 + Width = 338 Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] @@ -300,7 +300,7 @@ object frmPreferences: TfrmPreferences object comboIconPack: TComboBox Left = 220 Top = 309 - Width = 459 + Width = 451 Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] @@ -310,7 +310,7 @@ object frmPreferences: TfrmPreferences object comboWebSearchBaseUrl: TComboBox Left = 220 Top = 336 - Width = 459 + Width = 451 Height = 22 Anchors = [akLeft, akTop, akRight] TabOrder = 19 @@ -327,7 +327,7 @@ object frmPreferences: TfrmPreferences 'https://www.google.com/search?q=%query') end object chkThemePreview: TCheckBox - Left = 572 + Left = 564 Top = 284 Width = 97 Height = 17 @@ -342,7 +342,7 @@ object frmPreferences: TfrmPreferences ImageIndex = 56 ImageName = 'icons8-index' DesignSize = ( - 684 + 676 406) object Label4: TLabel Left = 8 @@ -485,7 +485,7 @@ object frmPreferences: TfrmPreferences object editLogDir: TButtonedEdit Left = 361 Top = 62 - Width = 318 + Width = 310 Height = 22 Anchors = [akLeft, akTop, akRight] Enabled = False @@ -564,7 +564,7 @@ object frmPreferences: TfrmPreferences ImageIndex = 57 ImageName = 'icons8-play' DesignSize = ( - 684 + 676 406) object lblFont: TLabel Left = 8 @@ -727,7 +727,7 @@ object frmPreferences: TfrmPreferences Width = 145 Height = 22 Style = csDropDownList - TabOrder = 15 + TabOrder = 14 OnChange = comboSQLColElementChange end object chkSQLBold: TCheckBox @@ -736,7 +736,7 @@ object frmPreferences: TfrmPreferences Width = 61 Height = 17 Caption = 'Bold' - TabOrder = 16 + TabOrder = 15 OnClick = SQLFontChange end object chkSQLItalic: TCheckBox @@ -745,7 +745,7 @@ object frmPreferences: TfrmPreferences Width = 50 Height = 17 Caption = 'Italic' - TabOrder = 17 + TabOrder = 16 OnClick = SQLFontChange end object cboxSQLColForeground: TColorBox @@ -755,7 +755,7 @@ object frmPreferences: TfrmPreferences Height = 22 NoneColorColor = clNone Style = [cbStandardColors, cbExtendedColors, cbSystemColors, cbIncludeNone, cbCustomColor, cbPrettyNames, cbCustomColors] - TabOrder = 18 + TabOrder = 17 OnChange = SQLFontChange end object cboxSQLColBackground: TColorBox @@ -765,14 +765,14 @@ object frmPreferences: TfrmPreferences Height = 22 NoneColorColor = clNone Style = [cbStandardColors, cbExtendedColors, cbSystemColors, cbIncludeNone, cbCustomColor, cbPrettyNames, cbCustomColors] - TabOrder = 19 + TabOrder = 18 OnChange = SQLFontChange end object SynMemoSQLSample: TSynMemo AlignWithMargins = True Left = 371 Top = 156 - Width = 308 + Width = 300 Height = 223 Cursor = crHandPoint SingleLineMode = False @@ -782,7 +782,7 @@ object frmPreferences: TfrmPreferences Font.Height = -13 Font.Name = 'Courier New' Font.Style = [] - TabOrder = 20 + TabOrder = 19 OnClick = SynMemoSQLSampleClick CodeFolding.GutterShapeSize = 11 CodeFolding.CollapsedLineColor = clGrayText @@ -810,24 +810,14 @@ object frmPreferences: TfrmPreferences OnChange = SQLFontChange FontSmoothing = fsmNone end - object chkQueryWarningsMessage: TCheckBox - Left = 220 - Top = 110 - Width = 459 - Height = 17 - Anchors = [akLeft, akTop, akRight] - Caption = 'Show query warnings dialog' - TabOrder = 12 - OnClick = Modified - end object chkAutoUppercase: TCheckBox Left = 220 Top = 133 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Auto uppercase reserved words and functions' - TabOrder = 13 + TabOrder = 12 OnClick = Modified end object comboEditorColorsPreset: TComboBox @@ -837,7 +827,7 @@ object frmPreferences: TfrmPreferences Height = 22 Style = csDropDownList ItemIndex = 0 - TabOrder = 14 + TabOrder = 13 Text = 'Current custom settings' OnChange = comboEditorColorsPresetChange Items.Strings = ( @@ -846,7 +836,7 @@ object frmPreferences: TfrmPreferences object chkCompletionProposalSearchOnMid: TCheckBox Left = 496 Top = 87 - Width = 183 + Width = 175 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Find matches in the middle' @@ -880,7 +870,7 @@ object frmPreferences: TfrmPreferences ImageIndex = 41 ImageName = 'icons8-data-grid' DesignSize = ( - 684 + 676 406) object lblMaxColWidth: TLabel Left = 8 @@ -1105,7 +1095,7 @@ object frmPreferences: TfrmPreferences object chkLocalNumberFormat: TCheckBox Left = 220 Top = 310 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Local number format' @@ -1115,7 +1105,7 @@ object frmPreferences: TfrmPreferences object chkHintsOnResultTabs: TCheckBox Left = 220 Top = 356 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Pop up SQL text over result tabs' @@ -1161,7 +1151,7 @@ object frmPreferences: TfrmPreferences object chkLowercaseHex: TCheckBox Left = 220 Top = 333 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Lowercase hexadecimal' @@ -1187,7 +1177,7 @@ object frmPreferences: TfrmPreferences object chkShowRowId: TCheckBox Left = 220 Top = 379 - Width = 461 + Width = 453 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Show static row id column' @@ -1200,7 +1190,7 @@ object frmPreferences: TfrmPreferences ImageIndex = 33 ImageName = 'icons8-compose' DesignSize = ( - 684 + 676 406) object lblLineBreakStyle: TLabel Left = 3 @@ -1212,7 +1202,7 @@ object frmPreferences: TfrmPreferences object chkEditorBinary: TCheckBox Left = 220 Top = 8 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Enable popup BLOB/HEX editor' @@ -1222,7 +1212,7 @@ object frmPreferences: TfrmPreferences object chkEditorDatetime: TCheckBox Left = 220 Top = 31 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Enable inplace date/time editor' @@ -1232,7 +1222,7 @@ object frmPreferences: TfrmPreferences object chkPrefillDateTime: TCheckBox Left = 220 Top = 54 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Prefill empty date/time fields with current date/time' @@ -1242,7 +1232,7 @@ object frmPreferences: TfrmPreferences object chkEditorEnum: TCheckBox Left = 220 Top = 77 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Enable ENUM pulldown editor' @@ -1252,7 +1242,7 @@ object frmPreferences: TfrmPreferences object chkEditorSet: TCheckBox Left = 220 Top = 100 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Enable SET checkbox editor' @@ -1262,7 +1252,7 @@ object frmPreferences: TfrmPreferences object chkReuseEditorConfiguration: TCheckBox Left = 220 Top = 163 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Remember filters, sorting and column selection across sessions' @@ -1272,7 +1262,7 @@ object frmPreferences: TfrmPreferences object chkForeignDropDown: TCheckBox Left = 220 Top = 186 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Show values in foreign key columns' @@ -1312,7 +1302,7 @@ object frmPreferences: TfrmPreferences ImageIndex = 13 ImageName = 'icons8-lightning-bolt-100' DesignSize = ( - 684 + 676 406) object lblShortcut1: TLabel Left = 306 @@ -1324,7 +1314,7 @@ object frmPreferences: TfrmPreferences object lblShortcutHint: TLabel Left = 306 Top = 3 - Width = 375 + Width = 367 Height = 55 Anchors = [akLeft, akTop, akRight] AutoSize = False @@ -1340,11 +1330,29 @@ object frmPreferences: TfrmPreferences end object TreeShortcutItems: TVirtualStringTree Left = 0 - Top = 0 + Top = 32 Width = 300 - Height = 406 - Align = alLeft + Height = 374 + Anchors = [akLeft, akTop, akBottom] + Colors.BorderColor = 15987699 + Colors.DisabledColor = clGray + Colors.DropMarkColor = 15385233 + Colors.DropTargetColor = 15385233 + Colors.DropTargetBorderColor = 15385233 + Colors.FocusedSelectionColor = 15385233 + Colors.FocusedSelectionBorderColor = 15385233 + Colors.GridLineColor = 15987699 + Colors.HeaderHotColor = clBlack + Colors.HotColor = clBlack + Colors.SelectionRectangleBlendColor = 15385233 + Colors.SelectionRectangleBorderColor = 15385233 + Colors.SelectionTextColor = clBlack + Colors.TreeLineColor = 9471874 + Colors.UnfocusedColor = clGray + Colors.UnfocusedSelectionColor = clWhite + Colors.UnfocusedSelectionBorderColor = clWhite Header.AutoSizeIndex = 0 + Header.Height = 14 Header.MainColumn = -1 Images = MainForm.VirtualImageListMain TabOrder = 0 @@ -1359,7 +1367,7 @@ object frmPreferences: TfrmPreferences Columns = <> end object btnRemoveHotKey1: TButton - Left = 596 + Left = 592 Top = 80 Width = 81 Height = 25 @@ -1371,7 +1379,7 @@ object frmPreferences: TfrmPreferences OnClick = btnRemoveHotKeyClick end object btnRemoveHotKey2: TButton - Left = 596 + Left = 592 Top = 144 Width = 81 Height = 25 @@ -1382,18 +1390,45 @@ object frmPreferences: TfrmPreferences TabOrder = 2 OnClick = btnRemoveHotKeyClick end + object editShortcutsFilter: TButtonedEdit + Left = 0 + Top = 4 + Width = 300 + Height = 22 + Images = MainForm.VirtualImageListMain + RightButton.ImageIndex = 193 + RightButton.Visible = True + TabOrder = 3 + TextHint = 'Filter' + OnChange = editShortcutsFilterChange + OnRightButtonClick = editShortcutsFilterRightButtonClick + end end object tabFiles: TTabSheet Caption = 'Files and tabs' ImageIndex = 10 ImageName = 'icons8-save-button-100' DesignSize = ( - 684 + 676 406) + object Label5: TLabel + Left = 8 + Top = 103 + Width = 148 + Height = 14 + Caption = 'Grayscale inactive tab icons' + end + object lblReformatter: TLabel + Left = 8 + Top = 131 + Width = 70 + Height = 14 + Caption = 'Reformatter:' + end object chkAskFileSave: TCheckBox Left = 220 Top = 8 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Prompt to save modified files on tab close' @@ -1405,7 +1440,7 @@ object frmPreferences: TfrmPreferences object chkRestoreTabs: TCheckBox Left = 220 Top = 31 - Width = 459 + Width = 451 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Reopen previously used SQL files and unsaved content in tabs *' @@ -1424,17 +1459,40 @@ object frmPreferences: TfrmPreferences object chkTabCloseOnMiddleClick: TCheckBox Left = 220 Top = 77 - Width = 457 + Width = 453 Height = 17 Anchors = [akLeft, akTop, akRight] Caption = 'Close tab on middleclick' TabOrder = 3 OnClick = Modified end + object comboTabIconsGrayscaleMode: TComboBox + Left = 220 + Top = 100 + Width = 451 + Height = 22 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + TabOrder = 4 + OnClick = Modified + Items.Strings = ( + 'Color icons on all tabs' + 'Grayscale icons on inactive query tabs only' + 'Grayscale icons on every inactive tab') + end + object comboReformatter: TComboBox + Left = 220 + Top = 128 + Width = 451 + Height = 22 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + TabOrder = 5 + end end end object btnCancel: TButton - Left = 545 + Left = 537 Top = 449 Width = 75 Height = 25 @@ -1445,7 +1503,7 @@ object frmPreferences: TfrmPreferences TabOrder = 2 end object btnOK: TButton - Left = 465 + Left = 457 Top = 449 Width = 75 Height = 25 @@ -1457,7 +1515,7 @@ object frmPreferences: TfrmPreferences OnClick = Apply end object btnApply: TButton - Left = 625 + Left = 617 Top = 449 Width = 75 Height = 25 diff --git a/source/preferences.pas b/source/preferences.pas index 6cf640dcb..8a6c15f53 100644 --- a/source/preferences.pas +++ b/source/preferences.pas @@ -13,7 +13,8 @@ interface Vcl.StdCtrls, Vcl.ComCtrls, Vcl.ExtCtrls, SynEditHighlighter, SynHighlighterSQL, SynEdit, SynMemo, VirtualTrees, SynEditKeyCmds, Vcl.ActnList, Vcl.StdActns, Vcl.Menus, dbstructures, gnugettext, Vcl.Themes, Vcl.Styles, SynRegExpr, System.Generics.Collections, - Vcl.ImageCollection, extra_controls, theme_preview, Vcl.Buttons, System.Actions; + Vcl.ImageCollection, extra_controls, theme_preview, reformatter, Vcl.Buttons, System.Actions, + VirtualTrees.BaseAncestorVCL, VirtualTrees.BaseTree, VirtualTrees.AncestorVCL, VirtualTrees.Types; type TShortcutItemData = record @@ -32,6 +33,7 @@ TGridColorsPreset = class TGridColorsPresetList = TObjectList; TfrmPreferences = class(TExtForm) + editShortcutsFilter: TButtonedEdit; pagecontrolMain: TPageControl; tabMisc: TTabSheet; btnCancel: TButton; @@ -147,7 +149,6 @@ TfrmPreferences = class(TExtForm) Label3: TLabel; cboxRowHighlightSameText: TColorBox; chkWheelZoom: TCheckBox; - chkQueryWarningsMessage: TCheckBox; chkAutoUppercase: TCheckBox; lblTheme: TLabel; comboTheme: TComboBox; @@ -188,6 +189,12 @@ TfrmPreferences = class(TExtForm) chkTabCloseOnMiddleClick: TCheckBox; btnRemoveHotKey1: TButton; btnRemoveHotKey2: TButton; + comboTabIconsGrayscaleMode: TComboBox; + Label5: TLabel; + lblReformatter: TLabel; + comboReformatter: TComboBox; + procedure editShortcutsFilterRightButtonClick(Sender: TObject); + procedure editShortcutsFilterChange(Sender: TObject); procedure FormShow(Sender: TObject); procedure Modified(Sender: TObject); procedure Apply(Sender: TObject); @@ -308,7 +315,6 @@ procedure TfrmPreferences.Apply(Sender: TObject); AppSettings.WriteBool(asAutoReconnect, chkAutoReconnect.Checked); AppSettings.WriteBool(asAllowMultipleInstances, chkAllowMultiInstances.Checked); AppSettings.WriteBool(asRestoreLastUsedDB, chkRestoreLastDB.Checked); - AppSettings.WriteBool(asQueryWarningsMessage, chkQueryWarningsMessage.Checked); AppSettings.WriteString(asFontName, comboSQLFontName.Text); AppSettings.WriteInt(asFontSize, updownSQLFontSize.Position); AppSettings.WriteInt(asTabWidth, updownSQLTabWidth.Position); @@ -440,6 +446,8 @@ procedure TfrmPreferences.Apply(Sender: TObject); AppSettings.WriteBool(asRestoreTabs, chkRestoreTabs.Checked); AppSettings.WriteBool(asTabCloseOnDoubleClick, chkTabCloseOnDoubleClick.Checked); AppSettings.WriteBool(asTabCloseOnMiddleClick, chkTabCloseOnMiddleClick.Checked); + AppSettings.WriteInt(asTabIconsGrayscaleMode, comboTabIconsGrayscaleMode.ItemIndex); + AppSettings.WriteInt(asReformatterNoDialog, comboReformatter.ItemIndex); // Set relevant properties in mainform MainForm.ApplyFontToGrids; @@ -514,6 +522,7 @@ procedure TfrmPreferences.FormCreate(Sender: TObject); Name: String; GridColorsPreset: TGridColorsPreset; IconPack: String; + Reformatter: TfrmReformatter; begin HasSizeGrip := True; @@ -527,14 +536,19 @@ procedure TfrmPreferences.FormCreate(Sender: TObject); InitLanguages; comboAppLanguage.Items.AddStrings(FLanguages); - comboGUIFont.Items.Assign(Screen.Fonts); - comboGUIFont.Items.Insert(0, '<'+_('Default system font')+'>'); + comboGUIFont.Items.Clear; + comboGUIFont.Items.Add('<'+_('Default system font')+'>'); + for i:=0 to Screen.Fonts.Count-1 do begin + if not Screen.Fonts[i].StartsWith('@') then + comboGUIFont.Items.Add(Screen.Fonts[i]); + end; Styles := TStyleManager.StyleNames; for i:=Low(Styles) to High(Styles) do begin comboTheme.Items.Add(Styles[i]); end; comboTheme.ItemIndex := comboTheme.Items.IndexOf(AppSettings.GetDefaultString(asTheme)); + lblTheme.Caption := lblTheme.Caption + ' ('+UpperCase(_('deprecated'))+')'; // Populate icon pack dropdown from image collections on main form comboIconPack.Items.Clear; @@ -556,18 +570,18 @@ procedure TfrmPreferences.FormCreate(Sender: TObject); comboSQLFontName.Sorted := True; updownCompletionProposalInterval.Min := 0; updownCompletionProposalInterval.Max := MaxInt; - SynMemoSQLSample.Text := 'SELECT DATE_SUB(NOW(), INTERVAL 1 DAY),' + CRLF + - #9'''String literal'' AS lit' + CRLF + - 'FROM tableA AS ta' + CRLF + - 'WHERE `columnA` IS NULL;' + CRLF + - CRLF + - '-- A comment' + CRLF + - '# Old style comment' + CRLF + - '/* Multi line comment */' + CRLF + - CRLF + - 'CREATE TABLE /*!32312 IF NOT EXISTS*/ tableB (' + CRLF + - #9'id INT,' + CRLF + - #9'name VARCHAR(30) DEFAULT "standard"' + CRLF + + SynMemoSQLSample.Text := 'SELECT DATE_SUB(NOW(), INTERVAL 1 DAY),' + sLineBreak + + CodeIndent + '''String literal'' AS lit' + sLineBreak + + 'FROM tableA AS ta' + sLineBreak + + 'WHERE `columnA` IS NULL;' + sLineBreak + + sLineBreak + + '-- A comment' + sLineBreak + + '# Old style comment' + sLineBreak + + '/* Multi line comment */' + sLineBreak + + sLineBreak + + 'CREATE TABLE /*!32312 IF NOT EXISTS*/ tableB (' + sLineBreak + + CodeIndent + 'id INT,' + sLineBreak + + CodeIndent + 'name VARCHAR(30) DEFAULT "standard"' + sLineBreak + ')'; SynSQLSynSQLSample.TableNames.CommaText := 'tableA,tableB'; for i:=0 to SynSQLSynSQLSample.AttrCount - 1 do begin @@ -671,6 +685,11 @@ procedure TfrmPreferences.FormCreate(Sender: TObject); FShortcutCategories.Add(_('SQL editing')); TreeShortcutItems.RootNodeCount := FShortcutCategories.Count; comboLineBreakStyle.Items := Explode(',', _('Windows linebreaks')+','+_('UNIX linebreaks')+','+_('Mac OS linebreaks')); + + comboReformatter.Items.Add(_('Always ask')); + Reformatter := TfrmReformatter.Create(Self); + comboReformatter.Items.AddStrings(Reformatter.grpReformatter.Items); + Reformatter.Free; end; @@ -716,7 +735,6 @@ procedure TfrmPreferences.FormShow(Sender: TObject); comboTheme.ItemIndex := comboTheme.Items.IndexOf(AppSettings.ReadString(asTheme)); comboIconPack.ItemIndex := comboIconPack.Items.IndexOf(AppSettings.ReadString(asIconPack)); comboWebSearchBaseUrl.Text := AppSettings.ReadString(asWebSearchBaseUrl); - chkQueryWarningsMessage.Checked := AppSettings.ReadBool(asQueryWarningsMessage); // Logging updownLogLines.Position := AppSettings.ReadInt(asLogsqlnum); @@ -753,7 +771,11 @@ procedure TfrmPreferences.FormShow(Sender: TObject); comboSQLColElementChange(Sender); // Grid formatting: - comboDataFontName.Items := Screen.Fonts; + comboDataFontName.Items.Clear; + for i:=0 to Screen.Fonts.Count-1 do begin + if not Screen.Fonts[i].StartsWith('@') then + comboDataFontName.Items.Add(Screen.Fonts[i]); + end; comboDataFontName.ItemIndex := comboDataFontName.Items.IndexOf(AppSettings.ReadString(asDataFontName)); updownDataFontSize.Position := AppSettings.ReadInt(asDataFontSize); updownMaxQueryResults.Position := AppSettings.ReadINt(asMaxQueryResults); @@ -804,6 +826,8 @@ procedure TfrmPreferences.FormShow(Sender: TObject); chkRestoreTabs.Checked := AppSettings.ReadBool(asRestoreTabs); chkTabCloseOnDoubleClick.Checked := AppSettings.ReadBool(asTabCloseOnDoubleClick); chkTabCloseOnMiddleClick.Checked := AppSettings.ReadBool(asTabCloseOnMiddleClick); + comboTabIconsGrayscaleMode.ItemIndex := AppSettings.ReadInt(asTabIconsGrayscaleMode); + comboReformatter.ItemIndex := AppSettings.ReadInt(asReformatterNoDialog); // Disable global shortcuts MainForm.ActionList1.State := asSuspended; @@ -815,6 +839,16 @@ procedure TfrmPreferences.FormShow(Sender: TObject); screen.Cursor := crdefault; end; +procedure TfrmPreferences.editShortcutsFilterRightButtonClick(Sender: TObject); +begin + editShortcutsFilter.Clear; +end; + +procedure TfrmPreferences.editShortcutsFilterChange(Sender: TObject); +begin + FilterNodesByEdit(editShortcutsFilter, TreeShortcutItems); +end; + procedure TfrmPreferences.SQLFontChange(Sender: TObject); @@ -1259,7 +1293,7 @@ procedure TfrmPreferences.TreeShortcutItemsGetText(Sender: TBaseVirtualTree; Nod end; CellText := _(CellText); end else if Assigned(Data.Action) then begin - CellText := MainForm.ActionList1DefaultCaptions[Data.Action.Index]; + CellText := StripHotkey(MainForm.ActionList1DefaultCaptions[Data.Action.Index]); end; end; end; @@ -1342,8 +1376,8 @@ function TfrmPreferences.EnsureShortcutIsUnused(RequestShortcut: TShortCut): Boo Data := Tree.GetNodeData(Node); Msg := Format(MsgFormat, [ ShortCutToText(RequestShortcut), - Tree.Text[Node, 0], - Tree.Text[NodeWantsIt, 0] + Tree.Text[Node.Parent, 0] + ' > ' + StripHotkey(Tree.Text[Node, 0]), + Tree.Text[NodeWantsIt.Parent, 0] + ' > ' + StripHotkey(Tree.Text[NodeWantsIt, 0]) ]); if Node = NodeWantsIt then begin // Ignore requesting node diff --git a/source/printlist.pas b/source/printlist.pas index 2b5b2b85a..23a3d255b 100644 --- a/source/printlist.pas +++ b/source/printlist.pas @@ -9,7 +9,7 @@ interface uses - Winapi.Windows, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Printers, VirtualTrees, gnugettext; + Winapi.Windows, System.Classes, System.SysUtils, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Printers, VirtualTrees, gnugettext; type TprintlistForm = class(TForm) @@ -43,7 +43,17 @@ procedure TprintlistForm.FormShow(Sender: TObject); // show! Screen.Cursor := crHourGlass; comboPrinters.Items := Printer.printers; - comboPrinters.ItemIndex := Printer.printerIndex; + try + if comboPrinters.Items.Count > 0 then begin + comboPrinters.ItemIndex := Printer.printerIndex; + btnConfigure.Enabled := True; + end; + except + on E:Exception do begin + btnConfigure.Enabled := False; + ErrorDialog(E.Message); + end; + end; Screen.Cursor := crDefault; end; diff --git a/source/reformatter.dfm b/source/reformatter.dfm index 174481027..929707153 100644 --- a/source/reformatter.dfm +++ b/source/reformatter.dfm @@ -3,7 +3,7 @@ object frmReformatter: TfrmReformatter Top = 0 BorderStyle = bsDialog Caption = 'Reformat SQL' - ClientHeight = 172 + ClientHeight = 217 ClientWidth = 503 Color = clBtnFace Font.Charset = DEFAULT_CHARSET @@ -16,13 +16,13 @@ object frmReformatter: TfrmReformatter OnDestroy = FormDestroy DesignSize = ( 503 - 172) + 217) TextHeight = 15 object grpReformatter: TRadioGroup Left = 8 Top = 8 Width = 487 - Height = 125 + Height = 128 Anchors = [akLeft, akTop, akRight, akBottom] Caption = 'Select reformatter' ItemIndex = 0 @@ -35,7 +35,7 @@ object frmReformatter: TfrmReformatter end object btnCancel: TButton Left = 420 - Top = 139 + Top = 184 Width = 75 Height = 25 Anchors = [akRight, akBottom] @@ -46,7 +46,7 @@ object frmReformatter: TfrmReformatter end object btnOk: TButton Left = 339 - Top = 139 + Top = 184 Width = 75 Height = 25 Anchors = [akRight, akBottom] @@ -59,12 +59,24 @@ object frmReformatter: TfrmReformatter object lblFormatProviderLink: TLinkLabel Left = 8 Top = 142 - Width = 121 - Height = 19 + Width = 149 + Height = 24 + Anchors = [akLeft, akBottom] Caption = 'lblFormatProviderLink' TabOrder = 3 UseVisualStyle = True Visible = False OnLinkClick = lblFormatProviderLinkLinkClick end + object chkKeepAsking: TCheckBox + Left = 8 + Top = 188 + Width = 257 + Height = 17 + Anchors = [akLeft, akRight, akBottom] + Caption = 'Keep asking this question.' + Checked = True + State = cbChecked + TabOrder = 4 + end end diff --git a/source/reformatter.pas b/source/reformatter.pas index b5603d9de..fa38613b8 100644 --- a/source/reformatter.pas +++ b/source/reformatter.pas @@ -13,6 +13,7 @@ TfrmReformatter = class(TExtForm) btnCancel: TButton; btnOk: TButton; lblFormatProviderLink: TLinkLabel; + chkKeepAsking: TCheckBox; procedure btnOkClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); @@ -63,6 +64,8 @@ procedure TfrmReformatter.btnOkClick(Sender: TObject); FOutputCode := FormatSqlOnlineSqlformatOrg(FInputCode); end; end; + // Unify line breaks, so selection end will be correct: + FOutputCode := fixNewlines(FOutputCode); TimeElapsed := GetTickCount64 - StartTime; MainForm.LogSQL(f_('Code reformatted in %s, using formatter %s', [FormatTimeNumber(TimeElapsed/1000, True, 3), '#'+grpReformatter.ItemIndex.ToString])); except @@ -76,6 +79,12 @@ procedure TfrmReformatter.btnOkClick(Sender: TObject); end; end; + + if not chkKeepAsking.Checked then begin + // No dialog next time please + AppSettings.WriteInt(asReformatterNoDialog, grpReformatter.ItemIndex+1); + end; + Screen.Cursor := crDefault; end; @@ -85,7 +94,13 @@ procedure TfrmReformatter.FormCreate(Sender: TObject); grpReformatter.Items.Add(_('Internal')); grpReformatter.Items.Add(f_('Online on %s', [APPDOMAIN])); grpReformatter.Items.Add(f_('Online on %s', ['sqlformat.org'])); - grpReformatter.ItemIndex := AppSettings.ReadInt(asReformatter); + if AppSettings.ReadInt(asReformatterNoDialog) = 0 then begin + grpReformatter.ItemIndex := AppSettings.ReadInt(asReformatter); + end + else begin + // asReformatterNoDialog has the same items with an additional "always ask" item at index 0 + grpReformatter.ItemIndex := AppSettings.ReadInt(asReformatterNoDialog) - 1; + end; end; procedure TfrmReformatter.FormDestroy(Sender: TObject); @@ -239,10 +254,7 @@ function TfrmReformatter.FormatSqlOnlineHeidisql(SQL: String): String; HttpReq.Request.CharSet := 'utf-8'; HttpReq.Request.UserAgent := apphelpers.UserAgent(Self); Parameters := TStringList.Create; - if AppSettings.ReadBool(asTabsToSpaces) then - Parameters.AddPair('indent', StringOfChar(' ', AppSettings.ReadInt(asTabWidth))) - else - Parameters.AddPair('indent', #9); + Parameters.AddPair('indent', CodeIndent); Parameters.AddPair('input', FInputCode); Result := HttpReq.Post(APPDOMAIN + 'sql-formatter.php', Parameters); if Result.IsEmpty then diff --git a/source/routine_editor.pas b/source/routine_editor.pas index d1a148738..6b56c94b3 100644 --- a/source/routine_editor.pas +++ b/source/routine_editor.pas @@ -4,8 +4,9 @@ interface uses Winapi.Windows, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, SynEdit, SynMemo, Vcl.StdCtrls, - Vcl.ComCtrls, Vcl.ToolWin, VirtualTrees, SynRegExpr, extra_controls, - dbconnection, apphelpers, gnugettext, Vcl.Menus, Vcl.ExtCtrls; + Vcl.ComCtrls, Vcl.ToolWin, VirtualTrees.BaseTree, VirtualTrees.Types, VirtualTrees, VirtualTrees.EditLink, SynRegExpr, extra_controls, + dbconnection, apphelpers, gnugettext, Vcl.Menus, Vcl.ExtCtrls, + VirtualTrees.BaseAncestorVCL, VirtualTrees.AncestorVCL; type TFrame = TDBObjectEditor; @@ -117,6 +118,7 @@ constructor TfrmRoutineEditor.Create(AOwner: TComponent); editName.MaxLength := NAME_LEN; FMainSynMemo := SynMemoBody; btnSave.Hint := ShortCutToText(MainForm.actSaveSQL.ShortCut); + FixVT(listParameters); end; @@ -125,7 +127,6 @@ procedure TfrmRoutineEditor.Init(Obj: TDBObject); i: Integer; begin inherited; - FixVT(listParameters); TExtForm.RestoreListSetup(listParameters); if Obj.NodeType = lntProcedure then FAlterRoutineType := 'PROCEDURE' else FAlterRoutineType := 'FUNCTION'; @@ -553,11 +554,11 @@ function TfrmRoutineEditor.ComposeCreateStatement(NameOfObject: String): String; tmp := ''; if ProcOrFunc = 'PROCEDURE' then tmp := tmp + Parameters[i].Context + ' '; - tmp := tmp + DBObject.Connection.QuoteIdent(Parameters[i].Name) + ' ' + Parameters[i].Datatype; + tmp := tmp + DBObject.Connection.QuoteIdent(Parameters[i].Name.Trim) + ' ' + Parameters[i].Datatype; Params.Add(tmp); end; if Params.Count > 0 then - Result := Result + CRLF + #9 + Implode(','+CRLF+#9, Params) + CRLF; + Result := Result + sLineBreak + CodeIndent + Implode(',' + sLineBreak + CodeIndent, Params) + sLineBreak; Result := Result + ')'+CRLF; if comboReturns.Enabled then Result := Result + 'RETURNS '+comboReturns.Text+CRLF; @@ -587,3 +588,4 @@ procedure TfrmRoutineEditor.btnHelpClick(Sender: TObject); end. + diff --git a/source/searchreplace.pas b/source/searchreplace.pas index e03b4b8ae..09e71f8df 100644 --- a/source/searchreplace.pas +++ b/source/searchreplace.pas @@ -461,7 +461,7 @@ procedure TfrmSearchReplace.DoSearchReplaceData; Inc(MatchCount); // Set focus on node and column - NodeSelected := SelectNode(Grid, Node, False); + NodeSelected := SelectNode(Grid, Node, not SelectedOnly); if not NodeSelected then Break; Grid.FocusedColumn := Column; diff --git a/source/selectdbobject.pas b/source/selectdbobject.pas index 8ac8f5888..9e5564e95 100644 --- a/source/selectdbobject.pas +++ b/source/selectdbobject.pas @@ -3,8 +3,9 @@ interface uses - Winapi.Windows, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls, VirtualTrees, VirtualTrees.Types, Vcl.Graphics, extra_controls, - dbconnection, gnugettext; + Winapi.Windows, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls, VirtualTrees.BaseTree, VirtualTrees.Types, VirtualTrees, Vcl.Graphics, extra_controls, + dbconnection, gnugettext, VirtualTrees.BaseAncestorVCL, + VirtualTrees.AncestorVCL; type TfrmSelectDBObject = class(TExtForm) diff --git a/source/sqlhelp.pas b/source/sqlhelp.pas index ae805f9b3..103a99031 100644 --- a/source/sqlhelp.pas +++ b/source/sqlhelp.pas @@ -6,7 +6,8 @@ interface Winapi.Windows, System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls, Vcl.ExtCtrls, Vcl.Buttons, SynMemo, SynEditHighlighter, SynHighlighterURI, extra_controls, SynURIOpener, SynEdit, VirtualTrees, Vcl.Graphics, - dbconnection, gnugettext; + dbconnection, gnugettext, VirtualTrees.BaseTree, VirtualTrees.Types, + VirtualTrees.BaseAncestorVCL, VirtualTrees.AncestorVCL, dbstructures; type TfrmSQLhelp = class(TExtForm) @@ -92,7 +93,7 @@ procedure TfrmSQLhelp.FormCreate(Sender: TObject); treeTopics.Clear; FreeAndNil(FRootTopics); FConnection := MainForm.ActiveConnection; - FRootTopics := FConnection.GetResults('HELP '+FConnection.EscapeString('CONTENTS')); + FRootTopics := FConnection.GetResults(FConnection.SqlProvider.GetSql(qHelpKeyword, [FConnection.EscapeString('CONTENTS')])); treeTopics.RootNodeCount := FRootTopics.RecordCount; end; @@ -123,14 +124,14 @@ procedure TfrmSQLhelp.treeTopicsFocusChanged(Sender: TBaseVirtualTree; Node: PVi if VT.HasChildren[VT.FocusedNode] then Exit; FKeyword := VT.Text[VT.FocusedNode, VT.FocusedColumn]; - lblKeyword.Caption := Copy(FKeyword, 0, 100); + lblKeyword.Caption := Copy(FKeyword, 1, 100); MemoDescription.Lines.Clear; MemoExample.Lines.Clear; Caption := DEFAULT_WINDOW_CAPTION; if FKeyword <> '' then try Screen.Cursor := crHourglass; - Results := FConnection.GetResults('HELP '+FConnection.EscapeString(FKeyword)); + Results := FConnection.GetResults(FConnection.SqlProvider.GetSql(qHelpKeyword, [FConnection.EscapeString(FKeyword)])); Caption := Caption + ' - ' + FKeyword; MemoDescription.Text := fixNewlines(Results.Col('description', True)); MemoExample.Text := fixNewlines(Results.Col('example', True)); @@ -217,7 +218,7 @@ procedure TfrmSQLhelp.treeTopicsInitChildren(Sender: TBaseVirtualTree; Node: PVi // Return number of children for folder VT := Sender as TVirtualStringTree; Results := VT.GetNodeData(Node); - Results^ := FConnection.GetResults('HELP '+FConnection.EscapeString(VT.Text[Node, VT.Header.MainColumn])); + Results^ := FConnection.GetResults(FConnection.SqlProvider.GetSql(qHelpKeyword, [FConnection.EscapeString(VT.Text[Node, VT.Header.MainColumn])])); ChildCount := Results.RecordCount; end; @@ -309,7 +310,7 @@ procedure TfrmSQLhelp.SetKeyword(Value: string); FKeyword := Value; if FKeyword = '' then Exit; - Results := FConnection.GetResults('HELP '+FConnection.EscapeString(FKeyword)); + Results := FConnection.GetResults(FConnection.SqlProvider.GetSql(qHelpKeyword, [FConnection.EscapeString(FKeyword)])); while not Results.Eof do begin if Results.Col('is_it_category', true) = 'N' then begin FKeyword := Results.Col('name'); diff --git a/source/syncdb.pas b/source/syncdb.pas index cc0fefb3b..28a2a1823 100644 --- a/source/syncdb.pas +++ b/source/syncdb.pas @@ -5,7 +5,8 @@ interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Generics.Collections, VirtualTrees, extra_controls, - dbconnection, gnugettext; + dbconnection, gnugettext, VirtualTrees.BaseTree, VirtualTrees.Types, + VirtualTrees.BaseAncestorVCL, VirtualTrees.AncestorVCL; type TfrmSyncDB = class(TExtForm) @@ -99,7 +100,7 @@ procedure TfrmSyncDB.FormCreate(Sender: TObject); SessNode: PVirtualNode; SessionPaths: TStringList; begin - Caption := MainForm.actSynchronizeDatabase.Caption; + //Caption := MainForm.actSynchronizeDatabase.Caption; HasSizeGrip := True; FixVT(treeSource); FixVT(treeDifferences); diff --git a/source/table_editor.dfm b/source/table_editor.dfm index e9d92e750..83aa2e49e 100644 --- a/source/table_editor.dfm +++ b/source/table_editor.dfm @@ -37,26 +37,26 @@ object frmTableEditor: TfrmTableEditor ImageName = 'icons8-data-sheet-100' DesignSize = ( 686 - 121) + 120) object lblName: TLabel Left = 4 Top = 6 - Width = 31 - Height = 13 + Width = 35 + Height = 15 Caption = 'Name:' end object lblComment: TLabel Left = 4 Top = 33 - Width = 49 - Height = 13 + Width = 57 + Height = 15 Caption = 'Comment:' end object editName: TEdit Left = 96 Top = 3 Width = 589 - Height = 21 + Height = 23 Anchors = [akLeft, akTop, akRight] TabOrder = 0 Text = 'editName' @@ -81,85 +81,129 @@ object frmTableEditor: TfrmTableEditor Caption = 'Options' ImageIndex = 39 ImageName = 'icons8-support' - DesignSize = ( - 686 - 121) object lblAutoinc: TLabel Left = 4 Top = 6 - Width = 77 - Height = 13 + Width = 86 + Height = 15 Caption = 'Auto increment:' end + object lblCollation: TLabel + Left = 358 + Top = 6 + Width = 90 + Height = 15 + Caption = 'Default collation:' + end object lblAvgRowLen: TLabel Left = 4 Top = 29 - Width = 99 - Height = 13 + Width = 106 + Height = 15 Caption = 'Average row length:' end - object lblInsertMethod: TLabel - Left = 294 - Top = 98 - Width = 79 - Height = 13 - Caption = 'INSERT method:' - end - object lblUnion: TLabel - Left = 294 - Top = 52 - Width = 63 - Height = 13 - Caption = 'Union tables:' + object lblEngine: TLabel + Left = 358 + Top = 29 + Width = 39 + Height = 15 + Caption = 'Engine:' end object lblMaxRows: TLabel Left = 4 Top = 52 - Width = 99 - Height = 13 + Width = 114 + Height = 15 Caption = 'Maximum row count:' end + object lblUnion: TLabel + Left = 358 + Top = 52 + Width = 69 + Height = 15 + Caption = 'Union tables:' + end object lblRowFormat: TLabel Left = 4 Top = 98 - Width = 60 - Height = 13 + Width = 65 + Height = 15 Caption = 'Row format:' end - object lblCollation: TLabel - Left = 294 - Top = 6 - Width = 81 - Height = 13 - Caption = 'Default collation:' + object lblInsertMethod: TLabel + Left = 358 + Top = 98 + Width = 85 + Height = 15 + Caption = 'INSERT method:' end - object lblEngine: TLabel - Left = 294 - Top = 29 - Width = 36 - Height = 13 - Caption = 'Engine:' + object editAutoInc: TEdit + Left = 178 + Top = 3 + Width = 155 + Height = 23 + TabOrder = 0 + OnChange = editNumEditChange + end + object comboCollation: TComboBox + Left = 464 + Top = 3 + Width = 102 + Height = 23 + Style = csDropDownList + DropDownCount = 16 + Sorted = True + TabOrder = 5 + OnChange = chkCharsetConvertClick + end + object chkCharsetConvert: TCheckBox + Left = 574 + Top = 5 + Width = 107 + Height = 17 + Caption = 'Convert data' + TabOrder = 6 + OnClick = chkCharsetConvertClick end object editAvgRowLen: TEdit Left = 178 Top = 26 - Width = 110 - Height = 21 + Width = 155 + Height = 23 TabOrder = 1 OnChange = editNumEditChange end + object comboEngine: TComboBox + Left = 464 + Top = 26 + Width = 221 + Height = 23 + Style = csDropDownList + TabOrder = 7 + OnSelect = comboEngineSelect + end object editMaxRows: TEdit Left = 178 Top = 49 - Width = 110 - Height = 21 + Width = 155 + Height = 23 TabOrder = 2 OnChange = editNumEditChange end + object memoUnionTables: TMemo + Left = 464 + Top = 49 + Width = 221 + Height = 44 + Lines.Strings = ( + 'memoUnion') + TabOrder = 8 + OnChange = Modification + end object chkChecksum: TCheckBox Left = 4 Top = 75 - Width = 189 + Width = 190 Height = 17 Alignment = taLeftJustify Caption = 'Checksum for rows:' @@ -169,72 +213,20 @@ object frmTableEditor: TfrmTableEditor object comboRowFormat: TComboBox Left = 178 Top = 95 - Width = 110 - Height = 21 + Width = 155 + Height = 23 Style = csDropDownList TabOrder = 4 OnChange = Modification end - object memoUnionTables: TMemo - Left = 408 - Top = 49 - Width = 277 - Height = 44 - Anchors = [akLeft, akTop, akRight] - Lines.Strings = ( - 'memoUnion') - TabOrder = 7 - OnChange = Modification - end object comboInsertMethod: TComboBox - Left = 408 + Left = 464 Top = 95 - Width = 277 - Height = 21 - Style = csDropDownList - Anchors = [akLeft, akTop, akRight] - TabOrder = 8 - OnClick = Modification - end - object editAutoInc: TEdit - Left = 178 - Top = 3 - Width = 110 - Height = 21 - TabOrder = 0 - OnChange = editNumEditChange - end - object comboCollation: TComboBox - Left = 408 - Top = 3 - Width = 158 - Height = 21 - Style = csDropDownList - Anchors = [akLeft, akTop, akRight] - DropDownCount = 16 - Sorted = True - TabOrder = 5 - OnChange = chkCharsetConvertClick - end - object comboEngine: TComboBox - Left = 408 - Top = 26 - Width = 277 - Height = 21 + Width = 221 + Height = 23 Style = csDropDownList - Anchors = [akLeft, akTop, akRight] - TabOrder = 6 - OnSelect = comboEngineSelect - end - object chkCharsetConvert: TCheckBox - Left = 574 - Top = 5 - Width = 107 - Height = 17 - Anchors = [akTop, akRight] - Caption = 'Convert data' TabOrder = 9 - OnClick = chkCharsetConvertClick + OnClick = Modification end end object tabIndexes: TTabSheet @@ -243,16 +235,17 @@ object frmTableEditor: TfrmTableEditor ImageName = 'icons8-lightning-bolt-100' object treeIndexes: TVirtualStringTree AlignWithMargins = True - Left = 69 + Left = 73 Top = 0 - Width = 614 - Height = 121 + Width = 610 + Height = 120 Margins.Top = 0 Margins.Bottom = 0 Align = alClient + DefaultNodeHeight = 19 DragMode = dmAutomatic EditDelay = 0 - Header.AutoSizeIndex = 0 + Header.AutoSizeIndex = -1 Header.Options = [hoAutoResize, hoColumnResize, hoDrag, hoShowSortGlyphs, hoVisible, hoDisableAnimatedResize, hoAutoResizeInclCaption] Header.PopupMenu = MainForm.popupListHeader Images = MainForm.VirtualImageListMain @@ -260,7 +253,7 @@ object frmTableEditor: TfrmTableEditor TabOrder = 1 TreeOptions.AutoOptions = [toAutoDropExpand, toAutoScrollOnExpand, toAutoTristateTracking, toAutoChangeScale] TreeOptions.MiscOptions = [toAcceptOLEDrop, toEditable, toFullRepaintOnResize, toGridExtensions, toInitOnSave, toToggleOnDblClick, toWheelPanning, toEditOnClick] - TreeOptions.PaintOptions = [toHotTrack, toShowButtons, toShowDropmark, toShowRoot, toShowTreeLines, toShowVertGridLines, toThemeAware, toUseBlendedImages, toFullVertGridLines, toUseExplorerTheme, toHideTreeLinesIfThemed] + TreeOptions.PaintOptions = [toHotTrack, toShowButtons, toShowDropmark, toShowRoot, toShowTreeLines, toShowVertGridLines, toThemeAware, toUseBlendedImages, toGhostedIfUnfocused, toFullVertGridLines, toUseExplorerTheme, toHideTreeLinesIfThemed] TreeOptions.SelectionOptions = [toExtendedFocus, toRightClickSelect] OnBeforePaint = treeIndexesBeforePaint OnClick = AnyTreeClick @@ -282,7 +275,7 @@ object frmTableEditor: TfrmTableEditor Options = [coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 0 Text = 'Name' - Width = 214 + Width = 176 end item Options = [coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] @@ -299,16 +292,25 @@ object frmTableEditor: TfrmTableEditor Position = 3 Text = 'Comment' Width = 120 + end + item + Position = 4 + Text = 'Direction' + Width = 80 + end + item + Position = 5 + Text = 'Visibility' end> end object tlbIndexes: TToolBar Left = 0 Top = 0 - Width = 66 - Height = 121 + Width = 70 + Height = 120 Align = alLeft AutoSize = True - ButtonWidth = 66 + ButtonWidth = 70 Caption = 'tlbIndexes' Images = MainForm.VirtualImageListMain List = True @@ -369,14 +371,23 @@ object frmTableEditor: TfrmTableEditor Caption = 'Foreign keys' ImageIndex = 136 ImageName = 'icons8-data-grid-relation' + object spltForeignKeyListings: TSplitter + Left = 573 + Top = 0 + Height = 120 + Align = alRight + Visible = False + ExplicitLeft = 494 + ExplicitTop = -3 + end object tlbForeignKeys: TToolBar Left = 0 Top = 0 - Width = 66 - Height = 121 + Width = 70 + Height = 120 Align = alLeft AutoSize = True - ButtonWidth = 66 + ButtonWidth = 70 Caption = 'tlbForeignKeys' Images = MainForm.VirtualImageListMain List = True @@ -408,19 +419,30 @@ object frmTableEditor: TfrmTableEditor Enabled = False ImageIndex = 26 ImageName = 'icons8-close-button' + Wrap = True OnClick = btnClearForeignKeysClick end + object btnShowReverseForeignKeys: TToolButton + Left = 0 + Top = 66 + Hint = 'Show reverse foreign keys' + Caption = 'Reverse' + ImageIndex = 40 + Style = tbsCheck + OnClick = btnShowReverseForeignKeysClick + end end object listForeignKeys: TVirtualStringTree - Left = 66 + Left = 70 Top = 0 - Width = 620 - Height = 121 + Width = 503 + Height = 120 Margins.Top = 0 Margins.Bottom = 0 Align = alClient + DefaultNodeHeight = 19 EditDelay = 0 - Header.AutoSizeIndex = 0 + Header.AutoSizeIndex = -1 Header.Options = [hoAutoResize, hoColumnResize, hoDrag, hoShowSortGlyphs, hoVisible, hoDisableAnimatedResize, hoAutoResizeInclCaption] Header.PopupMenu = MainForm.popupListHeader Images = MainForm.VirtualImageListMain @@ -475,8 +497,33 @@ object frmTableEditor: TfrmTableEditor Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 5 Text = 'On DELETE' - Width = 80 + Width = 10 + end> + end + object ListViewReverseForeignKeys: TListView + Left = 576 + Top = 0 + Width = 110 + Height = 120 + Align = alRight + Columns = < + item + AutoSize = True + Caption = 'Database' + end + item + AutoSize = True + Caption = 'Table' end> + ColumnClick = False + HotTrack = True + ReadOnly = True + RowSelect = True + SmallImages = MainForm.VirtualImageListMain + TabOrder = 2 + ViewStyle = vsReport + Visible = False + OnDblClick = ListViewReverseForeignKeysDblClick end end object tabCheckConstraints: TTabSheet @@ -485,11 +532,11 @@ object frmTableEditor: TfrmTableEditor object tlbCheckConstraints: TToolBar Left = 0 Top = 0 - Width = 66 - Height = 121 + Width = 70 + Height = 120 Align = alLeft AutoSize = True - ButtonWidth = 66 + ButtonWidth = 70 Caption = 'tlbCheckConstraints' Images = MainForm.VirtualImageListMain List = True @@ -522,11 +569,12 @@ object frmTableEditor: TfrmTableEditor end end object listCheckConstraints: TVirtualStringTree - Left = 66 + Left = 70 Top = 0 - Width = 620 - Height = 121 + Width = 616 + Height = 120 Align = alClient + DefaultNodeHeight = 19 EditDelay = 0 Header.AutoSizeIndex = 1 Header.Options = [hoAutoResize, hoColumnResize, hoDrag, hoShowSortGlyphs, hoVisible, hoDisableAnimatedResize, hoAutoResizeInclCaption] @@ -558,7 +606,7 @@ object frmTableEditor: TfrmTableEditor Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus, coEditable, coStyleColor] Position = 1 Text = 'Check clause' - Width = 416 + Width = 412 end> end end @@ -569,8 +617,8 @@ object frmTableEditor: TfrmTableEditor object SynMemoPartitions: TSynMemo Left = 0 Top = 0 - Width = 593 - Height = 121 + Width = 686 + Height = 120 SingleLineMode = False Align = alClient Font.Charset = DEFAULT_CHARSET @@ -611,8 +659,8 @@ object frmTableEditor: TfrmTableEditor object SynMemoCREATEcode: TSynMemo Left = 0 Top = 0 - Width = 593 - Height = 121 + Width = 686 + Height = 120 SingleLineMode = False Align = alClient Font.Charset = DEFAULT_CHARSET @@ -653,8 +701,8 @@ object frmTableEditor: TfrmTableEditor object SynMemoALTERcode: TSynMemo Left = 0 Top = 0 - Width = 593 - Height = 121 + Width = 686 + Height = 120 SingleLineMode = False Align = alClient Font.Charset = DEFAULT_CHARSET @@ -714,7 +762,7 @@ object frmTableEditor: TfrmTableEditor Margins.Bottom = 0 Align = alClient AutoSize = True - ButtonWidth = 66 + ButtonWidth = 70 Caption = 'Columns:' Images = MainForm.VirtualImageListMain List = True @@ -730,7 +778,7 @@ object frmTableEditor: TfrmTableEditor OnClick = btnAddColumnClick end object btnRemoveColumn: TToolButton - Left = 66 + Left = 70 Top = 0 Hint = 'Remove column' Caption = 'Remove' @@ -739,7 +787,7 @@ object frmTableEditor: TfrmTableEditor OnClick = btnRemoveColumnClick end object btnMoveUpColumn: TToolButton - Left = 132 + Left = 140 Top = 0 Hint = 'Move up' Caption = 'Up' @@ -748,7 +796,7 @@ object frmTableEditor: TfrmTableEditor OnClick = btnMoveUpColumnClick end object btnMoveDownColumn: TToolButton - Left = 198 + Left = 210 Top = 0 Hint = 'Move down' Caption = 'Down' @@ -767,10 +815,11 @@ object frmTableEditor: TfrmTableEditor Margins.Bottom = 32 Align = alClient Constraints.MinHeight = 20 + DefaultNodeHeight = 19 DragMode = dmAutomatic EditDelay = 0 Header.AutoSizeIndex = -1 - Header.Options = [hoColumnResize, hoDblClickResize, hoDrag, hoVisible, hoDisableAnimatedResize, hoAutoResizeInclCaption] + Header.Options = [hoColumnResize, hoDblClickResize, hoDrag, hoHotTrack, hoShowSortGlyphs, hoVisible, hoDisableAnimatedResize, hoAutoResizeInclCaption] Header.PopupMenu = MainForm.popupListHeader Images = MainForm.VirtualImageListMain IncrementalSearch = isAll @@ -793,88 +842,89 @@ object frmTableEditor: TfrmTableEditor OnGetText = listColumnsGetText OnPaintText = listColumnsPaintText OnGetNodeDataSize = listColumnsGetNodeDataSize + OnHeaderClick = listColumnsHeaderClick OnInitNode = listColumnsInitNode OnKeyPress = listColumnsKeyPress OnNewText = listColumnsNewText - OnNodeMoved = listColumnsNodeMoved Touch.InteractiveGestures = [igPan, igPressAndTap] Touch.InteractiveGestureOptions = [igoPanSingleFingerHorizontal, igoPanSingleFingerVertical, igoPanInertia, igoPanGutter, igoParentPassthrough] Columns = < item Alignment = taRightJustify MinWidth = 20 - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coShowDropMark, coVisible, coAllowFocus] Position = 0 Text = '#' Width = 20 end item MinWidth = 50 - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 1 Text = 'Name' Width = 100 end item - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 2 Text = 'Datatype' Width = 90 end item - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 3 Text = 'Length/Set' Width = 90 end item - Alignment = taCenter - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 4 Text = 'Unsigned' Width = 60 end item - Alignment = taCenter - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 5 Text = 'Allow NULL' Width = 65 end item - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 6 Text = 'Zerofill' end item - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 7 Text = 'Default' Width = 100 end item - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 8 Text = 'Comment' Width = 130 end item - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 9 Text = 'Collation' Width = 100 end item - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 10 Text = 'Expression' Width = 100 end item - Options = [coDraggable, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible, coAllowFocus] Position = 11 Text = 'Virtuality' Width = 100 + end + item + Hint = 'Spatial reference system' + Position = 12 + Text = 'SRID' + end + item + Hint = 'Hide in certain contexts' + Position = 13 + Text = 'Invisible' + end + item + Hint = 'Storage-Engine Independent Column Compression' + Position = 14 + Text = 'Compressed' end> end object btnSave: TButton diff --git a/source/table_editor.pas b/source/table_editor.pas index 6ac8daabf..de9633b44 100644 --- a/source/table_editor.pas +++ b/source/table_editor.pas @@ -6,7 +6,8 @@ interface Winapi.Windows, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls, Vcl.ToolWin, VirtualTrees, VirtualTrees.Types, SynRegExpr, Winapi.ActiveX, Vcl.ExtCtrls, SynEdit, SynMemo, Vcl.Menus, Vcl.Clipbrd, System.Math, System.UITypes, System.Generics.Collections, - grideditlinks, dbstructures, dbstructures.mysql, dbconnection, apphelpers, gnugettext, System.StrUtils, extra_controls; + grideditlinks, dbstructures, dbstructures.mysql, dbconnection, apphelpers, gnugettext, System.StrUtils, extra_controls, + VirtualTrees.BaseAncestorVCL, VirtualTrees.BaseTree, VirtualTrees.AncestorVCL; type TFrame = TDBObjectEditor; @@ -15,7 +16,9 @@ TfrmTableEditor = class(TFrame) btnDiscard: TButton; btnHelp: TButton; listColumns: TVirtualStringTree; + ListViewReverseForeignKeys: TListView; PageControlMain: TPageControl; + spltForeignKeyListings: TSplitter; tabBasic: TTabSheet; tabIndexes: TTabSheet; tabOptions: TTabSheet; @@ -40,6 +43,7 @@ TfrmTableEditor = class(TFrame) comboCollation: TComboBox; lblEngine: TLabel; comboEngine: TComboBox; + btnShowReverseForeignKeys: TToolButton; treeIndexes: TVirtualStringTree; tlbIndexes: TToolBar; btnAddIndex: TToolButton; @@ -93,6 +97,8 @@ TfrmTableEditor = class(TFrame) btnClearCheckConstraints: TToolButton; listCheckConstraints: TVirtualStringTree; Copy1: TMenuItem; + procedure btnShowReverseForeignKeysClick(Sender: TObject); + procedure ListViewReverseForeignKeysDblClick(Sender: TObject); procedure Modification(Sender: TObject); procedure btnAddColumnClick(Sender: TObject); procedure btnRemoveColumnClick(Sender: TObject); @@ -106,7 +112,7 @@ TfrmTableEditor = class(TFrame) procedure btnMoveDownColumnClick(Sender: TObject); procedure listColumnsDragOver(Sender: TBaseVirtualTree; Source: TObject; Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode; var Effect: Integer; var Accept: Boolean); - procedure listColumnsDragDrop(Sender: TBaseVirtualTree; Source: TObject; DataObject: IDataObject; Formats: TFormatArray; + procedure listColumnsDragDrop(Sender: TBaseVirtualTree; Source: TObject; DataObject: TVTDragDataObject; Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode); procedure listColumnsPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); @@ -134,7 +140,7 @@ TfrmTableEditor = class(TFrame) Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode; var Effect: Integer; var Accept: Boolean); procedure treeIndexesDragDrop(Sender: TBaseVirtualTree; Source: TObject; - DataObject: IDataObject; Formats: TFormatArray; Shift: TShiftState; + DataObject: TVTDragDataObject; Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode); procedure treeIndexesNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; NewText: String); procedure treeIndexesEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); @@ -171,7 +177,6 @@ TfrmTableEditor = class(TFrame) procedure listColumnsInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); procedure listColumnsGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); - procedure listColumnsNodeMoved(Sender: TBaseVirtualTree; Node: PVirtualNode); procedure listColumnsKeyPress(Sender: TObject; var Key: Char); procedure vtHandleClickOrKeyPress(Sender: TVirtualStringTree; Node: PVirtualNode; Column: TColumnIndex; HitPositions: THitPositions); @@ -201,18 +206,43 @@ TfrmTableEditor = class(TFrame) procedure menuRemovePropertyClick(Sender: TObject); procedure menuClearPropertiesClick(Sender: TObject); procedure menuAddPropertyClick(Sender: TObject); + procedure listColumnsHeaderClick(Sender: TVTHeader; + HitInfo: TVTHeaderHitInfo); private { Private declarations } FLoaded: Boolean; CreateCodeValid, AlterCodeValid: Boolean; + FReverseForeignKeysLoaded: Boolean; FColumns: TTableColumnList; - FKeys: TTableKeyList; + FKeys, FDeletedKeys: TTableKeyList; FForeignKeys: TForeignKeyList; FCheckConstraints: TCheckConstraintList; - FDeletedKeys, FDeletedForeignKeys, FDeletedCheckConstraints: TStringList; FAlterRestrictedMessageDisplayed: Boolean; + const ColNumCounter = 0; + const ColNumName = 1; + const ColNumDatatype = 2; + const ColNumLengthSet = 3; + const ColNumUnsigned = 4; + const ColNumAllownull = 5; + const ColNumZerofill = 6; + const ColNumDefault = 7; + const ColNumComment = 8; + const ColNumCollation = 9; + const ColNumExpression = 10; + const ColNumVirtuality = 11; + const ColNumSrid = 12; + const ColNumInvisible = 13; + const ColNumCompressed = 14; + const ColNumsCheckboxes = [ColNumUnsigned, ColNumAllownull, ColNumZerofill, ColNumInvisible, ColNumCompressed]; + // Columns in index tree + const IndexColNumName = 0; + const IndexColNumType = 1; + const IndexColNumAlgorithm = 2; + const IndexColNumComment = 3; + const IndexColNumDirection = 4; + const IndexColNumVisibility = 5; procedure ValidateColumnControls; procedure ValidateIndexControls; procedure MoveFocusedIndexPart(NewIdx: Cardinal); @@ -224,6 +254,8 @@ TfrmTableEditor = class(TFrame) function GetKeyImageIndexes(Col: TTableColumn): TList; procedure CalcMinColWidth; procedure UpdateTabCaptions; + function MoveNodeAllowed(Sender: TVirtualStringTree): Boolean; + procedure LoadReverseForeignKeys(Sender: TObject); public { Public declarations } constructor Create(AOwner: TComponent); override; @@ -242,6 +274,8 @@ implementation constructor TfrmTableEditor.Create(AOwner: TComponent); +var + i: Integer; begin inherited; comboRowFormat.Items.CommaText := 'DEFAULT,DYNAMIC,FIXED,COMPRESSED,REDUNDANT,COMPACT'; @@ -249,13 +283,34 @@ constructor TfrmTableEditor.Create(AOwner: TComponent); FColumns := TTableColumnList.Create; FKeys := TTableKeyList.Create; FForeignKeys := TForeignKeyList.Create; - FDeletedKeys := TStringList.Create; + FDeletedKeys := TTableKeyList.Create; FDeletedForeignKeys := TStringList.Create; FDeletedCheckConstraints := TStringList.Create; FDeletedCheckConstraints.Duplicates := dupIgnore; editName.MaxLength := NAME_LEN; FAlterRestrictedMessageDisplayed := False; btnSave.Hint := ShortCutToText(MainForm.actSaveSQL.ShortCut); + listColumns.OnCompareNodes := MainForm.AnyGridCompareNodes; + //listColumns.OnHeaderClick has its own handler + listColumns.OnAfterPaint := MainForm.AnyGridAfterPaint; + // Hide 0/1 text behind the drawn checkbox, by centering the text like the checkbox. We need the 0/1 text for sorting. + // And we cannot over-draw the cell rect which may have a different background. + for i in ColNumsCheckboxes do begin + listColumns.Header.Columns[i].Alignment := taCenter; + end; + btnShowReverseForeignKeys.Down := AppSettings.ReadBool(asDisplayReverseForeignKeys); + FixVT(listColumns); + FixVT(treeIndexes); + FixVT(listForeignKeys); + FixVT(listCheckConstraints); +end; + + +procedure TfrmTableEditor.listColumnsHeaderClick(Sender: TVTHeader; + HitInfo: TVTHeaderHitInfo); +begin + MainForm.AnyGridHeaderClick(Sender, HitInfo); + ValidateColumnControls; end; @@ -267,10 +322,6 @@ procedure TfrmTableEditor.Init(Obj: TDBObject); inherited; FLoaded := False; - FixVT(listColumns); - FixVT(treeIndexes); - FixVT(listForeignKeys); - FixVT(listCheckConstraints); // Try the best to auto fit various column widths, respecting a custom DPI setting and a pulldown arrow listColumns.Header.Columns[2].Width := Mainform.Canvas.TextWidth('GEOMETRYCOLLECTION') + 6*listColumns.TextMargin; listColumns.Header.Columns[7].Width := Mainform.Canvas.TextWidth('AUTO_INCREMENT') + 4*listColumns.TextMargin; @@ -280,18 +331,31 @@ procedure TfrmTableEditor.Init(Obj: TDBObject); TExtForm.RestoreListSetup(treeIndexes); TExtForm.RestoreListSetup(listForeignKeys); TExtForm.RestoreListSetup(listCheckConstraints); + // Fix control width and position, broken when opening a second table. See issue #1959 + comboCollation.Left := lblCollation.Left + TExtForm.ScaleSize(150, Self); + comboCollation.Width := comboRowFormat.Width; + comboCollation.Items := DBObject.Connection.CollationList; + chkCharsetConvert.Left := comboCollation.Left + comboCollation.Width + 10; + comboEngine.Left := comboCollation.Left; + comboEngine.Width := comboCollation.Width; comboEngine.Items := DBObject.Connection.TableEngines; comboEngine.Items.Insert(0, '<'+_('Server default')+'>'); comboEngine.ItemIndex := 0; - comboCollation.Items := DBObject.Connection.CollationList; + memoUnionTables.Left := comboCollation.Left; + memoUnionTables.Width := comboCollation.Width; + comboInsertMethod.Left := comboCollation.Left; + comboInsertMethod.Width := comboCollation.Width; if DBObject.Connection.Parameters.IsMariaDB then begin with listColumns.Header do begin - Columns[10].Options := Columns[10].Options + [coVisible]; - Columns[11].Options := Columns[11].Options + [coVisible]; + Columns[ColNumExpression].Options := Columns[ColNumExpression].Options + [coVisible]; + Columns[ColNumVirtuality].Options := Columns[ColNumVirtuality].Options + [coVisible]; + Columns[ColNumCompressed].Options := Columns[ColNumCompressed].Options + [coVisible]; end; end; listColumns.BeginUpdate; listColumns.Clear; + listColumns.Header.SortColumn := 0; + listColumns.Header.SortDirection := sdAscending; treeIndexes.Clear; listForeignKeys.Clear; listCheckConstraints.Clear; @@ -393,6 +457,8 @@ procedure TfrmTableEditor.Init(Obj: TDBObject); ResetModificationFlags; CreateCodeValid := False; AlterCodeValid := False; + FReverseForeignKeysLoaded := False; + btnShowReverseForeignKeysClick(Self); PageControlMainChange(Self); // Foreign key editor needs a hit // Buttons are randomly moved, since VirtualTree update, see #440 btnSave.Top := Height - btnSave.Height - 3; @@ -448,7 +514,7 @@ function TfrmTableEditor.ApplyModifications: TModalResult; Batch: TSQLBatch; Query: TSQLSentence; i: Integer; - Rename: String; + Rename, ErrMessage, ErrMessageAdditional, InnodbStatus: String; begin // Check if all indexes have at least one column // If not, exit early @@ -468,12 +534,18 @@ function TfrmTableEditor.ApplyModifications: TModalResult; else Batch := ComposeAlterStatement; try - for Query in Batch do + for Query in Batch do begin DBObject.Connection.Query(Query.SQL); + DBObject.Connection.ShowWarnings; + end; // Rename table if ObjectExists and (editName.Text <> DBObject.Name) then begin - Rename := DBObject.Connection.GetSQLSpecifity(spRenameTable, [DBObject.QuotedName, DBObject.Connection.QuoteIdent(editName.Text)]); + Rename := DBObject.Connection.SqlProvider.GetSql(qRenameTable, [ + DBObject.QuotedName(True, False), + DBObject.Connection.QuoteIdent(editName.Text) + ]); DBObject.Connection.Query(Rename); + DBObject.Connection.ShowWarnings; end; tabALTERcode.TabVisible := ObjectExists; if chkCharsetConvert.Checked then begin @@ -496,7 +568,18 @@ function TfrmTableEditor.ApplyModifications: TModalResult; CreateCodeValid := False; except on E:EDbError do begin - ErrorDialog(E.Message); + ErrMessage := E.Message; + // Help user with a cryptic error message, by getting details from INNODB STATUS + // See https://stackoverflow.com/questions/8434518/mysql-foreign-key-constraint-is-incorrectly-formed-error/64251639 + if DBObject.Connection.Parameters.IsAnyMySQL + and ContainsText(ErrMessage, 'constraint is incorrectly formed') then + begin + InnodbStatus := DBObject.Connection.GetVar('SHOW ENGINE INNODB STATUS', 'Status'); + ErrMessageAdditional := RegExprGetMatch('\n([^\n]+ constraint failed\.[^\n]+)\n', InnodbStatus, 1, False, True); + if not ErrMessageAdditional.IsEmpty then + ErrMessage := ErrMessage + sLineBreak + sLineBreak + 'INNODB STATUS:' + sLineBreak + ErrMessageAdditional; + end; + ErrorDialog(ErrMessage); Result := mrAbort; end; end; @@ -555,7 +638,8 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; AlterColBase, AddColBase: String; i: Integer; Results: TDBQuery; - Col, PreviousCol: PTableColumn; + Col, PreviousCol: TTableColumn; + TblKey: TTableKey; Constraint: TCheckConstraint; Node: PVirtualNode; Conn: TDBConnection; @@ -563,7 +647,8 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; procedure FinishSpecs; begin if Specs.Count > 0 then begin - SQL := SQL + Trim('ALTER TABLE '+DBObject.QuotedName + CRLF + #9 + Implode(',' + CRLF + #9, Specs)) + ';' + CRLF; + SQL := SQL + Trim('ALTER TABLE '+DBObject.QuotedName + sLineBreak + + CodeIndent + Implode(',' + sLineBreak + CodeIndent, Specs)) + ';' + sLineBreak; Specs.Clear; end; end; @@ -588,7 +673,7 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; // ALTER TABLE statement. Separate statements are required." for i:=0 to FForeignKeys.Count-1 do begin if FForeignKeys[i].Modified and (not FForeignKeys[i].Added) then - Specs.Add('DROP FOREIGN KEY '+Conn.QuoteIdent(FForeignKeys[i].OldKeyName)); + Specs.Add(Conn.SqlProvider.GetSql(qForeignKeyDrop, [Conn.QuoteIdent(FForeignKeys[i].OldKeyName)])); end; FinishSpecs; @@ -642,7 +727,7 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; Results := Conn.CollationTable; if Assigned(Results) then while not Results.Eof do begin if Results.Col('Collation') = comboCollation.Text then begin - Specs.Add('CONVERT TO CHARSET '+Results.Col('Charset')); + Specs.Add('CONVERT TO CHARSET '+Results.Col('Charset')+' COLLATE '+Conn.EscapeString(comboCollation.Text)); break; end; Results.Next; @@ -652,12 +737,11 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; // Update columns Node := listColumns.GetFirst; PreviousCol := nil; - while Assigned(Node) do begin - Col := listColumns.GetNodeData(Node); + for Col in FColumns do begin if Col.Status <> esUntouched then begin OverrideCollation := IfThen(chkCharsetConvert.Checked, comboCollation.Text); - AlterColBase := Conn.GetSQLSpecifity(spChangeColumn); - AddColBase := Conn.GetSQLSpecifity(spAddColumn); + AlterColBase := Conn.SqlProvider.GetSql(qChangeColumn); + AddColBase := Conn.SqlProvider.GetSql(qAddColumn); case Conn.Parameters.NetTypeGroup of @@ -684,7 +768,7 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; ColSpec := Col.SQLCode(OverrideCollation); case Col.Status of esModified: begin - Specs.Add(Format(AlterColBase, [Conn.QuoteIdent(Col.OldName), ColSpec])); + Specs.Add(Format(AlterColBase, ['', ColSpec])); end; esAddedUntouched, esAddedModified: begin Specs.Add(Format(AddColBase, [ColSpec])); @@ -708,7 +792,7 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; if Col.Name <> Col.OldName then begin FinishSpecs; Specs.Add( - Conn.GetSQLSpecifity(spRenameColumn, [Conn.QuoteIdent(Col.OldName), Conn.QuoteIdent(Col.Name)]) + Conn.SqlProvider.GetSql(qRenameColumn, [Conn.QuoteIdent(Col.OldName), Conn.QuoteIdent(Col.Name)]) ); FinishSpecs; end; @@ -744,7 +828,7 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; // Rename if Col.Name <> Col.OldName then begin Specs.Add( - Conn.GetSQLSpecifity(spRenameColumn, [Conn.QuoteIdent(Col.OldName), Conn.QuoteIdent(Col.Name)]) + Conn.SqlProvider.GetSql(qRenameColumn, [Conn.QuoteIdent(Col.OldName), Conn.QuoteIdent(Col.Name)]) ); end; end; @@ -759,42 +843,63 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; end; PreviousCol := Col; - Node := listColumns.GetNextSibling(Node); end; - // Deleted columns, not available as Node in above loop - for i:=0 to FColumns.Count-1 do begin - if FColumns[i].Status = esDeleted then begin - Specs.Add('DROP COLUMN '+Conn.QuoteIdent(FColumns[i].OldName)); + // Deleted columns + for Col in FColumns do begin + if Col.Status = esDeleted then begin + Specs.Add('DROP COLUMN '+Conn.QuoteIdent(Col.OldName)); // MSSQL + SQLite want one ALTER TABLE query per DROP COLUMN if Conn.Parameters.NetTypeGroup in [ngMSSQL, ngSQLite] then FinishSpecs; end; end; - // Drop indexes, also changed indexes, which will be readded below - for i:=0 to FDeletedKeys.Count-1 do begin - if FDeletedKeys[i] = TTableKey.PRIMARY then - IndexSQL := 'PRIMARY KEY' - else - IndexSQL := 'INDEX ' + Conn.QuoteIdent(FDeletedKeys[i]); - Specs.Add('DROP '+IndexSQL); - end; - // Add changed or added indexes - for i:=0 to FKeys.Count-1 do begin - if FKeys[i].Modified and (not FKeys[i].Added) then begin - if FKeys[i].OldIndexType = TTableKey.PRIMARY then + // Drop indexes + for TblKey in FDeletedKeys do begin + if not TblKey.InsideCreateCode then + Continue; + if Conn.Parameters.IsAnyPostgreSQL then begin + if TblKey.IsPrimary or TblKey.IsUnique then + IndexSQL := 'CONSTRAINT ' + TblKey.OldName + else // wrong: + IndexSQL := 'INDEX ' + Conn.QuoteIdent(TblKey.OldName); + end + else begin + if TblKey.IsPrimary then IndexSQL := 'PRIMARY KEY' else - IndexSQL := 'INDEX ' + Conn.QuoteIdent(FKeys[i].OldName); + IndexSQL := 'INDEX ' + Conn.QuoteIdent(TblKey.OldName); + end; + Specs.Add('DROP '+IndexSQL); + end; + + // Drop changed indexes, and add changed or added indexes + for TblKey in FKeys do begin + if not TblKey.InsideCreateCode then + Continue; + if TblKey.Modified and (not TblKey.Added) then begin + if Conn.Parameters.IsAnyPostgreSQL then begin + if (TblKey.OldIndexType = TTableKey.PRIMARY) or (TblKey.OldIndexType = TTableKey.UNIQUE) then + IndexSQL := 'CONSTRAINT ' + TblKey.OldName + else // wrong: + IndexSQL := 'INDEX ' + Conn.QuoteIdent(TblKey.OldName); + end + else begin + if TblKey.OldIndexType = TTableKey.PRIMARY then + IndexSQL := 'PRIMARY KEY' + else + IndexSQL := 'INDEX ' + Conn.QuoteIdent(TblKey.OldName); + end; Specs.Add('DROP '+IndexSQL); end; - if FKeys[i].Added or FKeys[i].Modified then - Specs.Add('ADD '+FKeys[i].SQLCode); + if TblKey.Added or TblKey.Modified then + Specs.Add('ADD '+TblKey.SQLCode); end; - for i:=0 to FDeletedForeignKeys.Count-1 do - Specs.Add('DROP FOREIGN KEY '+Conn.QuoteIdent(FDeletedForeignKeys[i])); + for i:=0 to FDeletedForeignKeys.Count-1 do begin + Specs.Add(Conn.SqlProvider.GetSql(qForeignKeyDrop, [Conn.QuoteIdent(FDeletedForeignKeys[i])])); + end; for i:=0 to FForeignKeys.Count-1 do begin if FForeignKeys[i].Added or FForeignKeys[i].Modified then Specs.Add('ADD '+FForeignKeys[i].SQLCode(True)); @@ -809,10 +914,54 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; Specs.Add('ADD ' + Constraint.SQLCode); end; - FinishSpecs; - Result := TSQLBatch.Create; + // Separate ALTER TABLE .. ALTER INDEX query for visible/invisible indexes features, which gets otherwise + // ignored in MySQL and MariaDB when done by a drop + add combined query. See issue #1388 + if Conn.SqlProvider.Has(qIndexInvisible) then begin + for i:=0 to FKeys.Count-1 do begin + if FKeys[i].Modified then begin + Specs.Add('ALTER INDEX ' + Conn.QuoteIdent(FKeys[i].Name) + ' ' + + IfThen( + FKeys[i].Visible, + Conn.SqlProvider.GetSql(qIndexVisible), + Conn.SqlProvider.GetSql(qIndexInvisible) + ) + ); + end; + end; + FinishSpecs; + end; + + + // *** Separate queries from here on + + // Drop indexes, also changed indexes, which will be readded below + for i:=0 to FDeletedKeys.Count-1 do begin + if FDeletedKeys[i].InsideCreateCode then + Continue; + if FDeletedKeys[i].IsPrimary then + IndexSQL := 'PRIMARY KEY' + else + IndexSQL := 'INDEX ' + Conn.QuoteIdent(FDeletedKeys[i].OldName); + AddQuery('DROP '+IndexSQL); + end; + // Add changed or added indexes + for i:=0 to FKeys.Count-1 do begin + if FKeys[i].InsideCreateCode then + Continue; + if FKeys[i].Modified and (not FKeys[i].Added) then begin + if FKeys[i].OldIndexType = TTableKey.PRIMARY then + IndexSQL := 'PRIMARY KEY' + else + IndexSQL := 'INDEX ' + Conn.QuoteIdent(FKeys[i].OldName); + AddQuery('DROP '+IndexSQL); + end; + if FKeys[i].Added or FKeys[i].Modified then + AddQuery(FKeys[i].SQLCode(DBObject.Name)); + end; + + Result := TSQLBatch.Create(DBObject.Connection.Parameters.NetTypeGroup); Result.SQL := SQL; FreeAndNil(Specs); @@ -823,82 +972,90 @@ function TfrmTableEditor.ComposeAlterStatement: TSQLBatch; function TfrmTableEditor.ComposeCreateStatement: TSQLBatch; var - i, IndexCount: Integer; - Col: PTableColumn; + i: Integer; + Col: TTableColumn; Constraint: TCheckConstraint; - Node: PVirtualNode; tmp, SQL: String; + CreateLines: TStringList; begin // Compose CREATE query, called by buttons and for SQL code tab - SQL := 'CREATE TABLE '+DBObject.Connection.QuoteIdent(editName.Text)+' ('+CRLF; - Node := listColumns.GetFirst; - while Assigned(Node) do begin - Col := listColumns.GetNodeData(Node); - SQL := SQL + #9 + Col.SQLCode + ','+CRLF; - Node := listColumns.GetNextSibling(Node); + SQL := 'CREATE TABLE '+DBObject.Connection.QuoteIdent(editName.Text)+' ('+ sLineBreak; + CreateLines := TStringList.Create; + // Lines with columns, indexes, foreign keys and check constraints + for Col in FColumns do begin + if not (Col.Status in [esDeleted, esAddedDeleted]) then + CreateLines.Add(Col.SQLCode); end; - - IndexCount := 0; for i:=0 to FKeys.Count-1 do begin + if not FKeys[i].InsideCreateCode then + Continue; tmp := FKeys[i].SQLCode; if tmp <> '' then begin - SQL := SQL + #9 + tmp + ','+CRLF; - Inc(IndexCount); + CreateLines.Add(tmp); end; end; - - for i:=0 to FForeignKeys.Count-1 do - SQL := SQL + #9 + FForeignKeys[i].SQLCode(True) + ','+CRLF; - - // Check constraints + for i:=0 to FForeignKeys.Count-1 do begin + CreateLines.Add(FForeignKeys[i].SQLCode(True)); + end; for Constraint in FCheckConstraints do begin - SQL := SQL + #9 + Constraint.SQLCode + ',' + sLineBreak; + CreateLines.Add(Constraint.SQLCode); end; + SQL := SQL + CodeIndent + Implode(',' + sLineBreak + CodeIndent, CreateLines) + sLineBreak + + ')' + sLineBreak; + CreateLines.Free; - if Integer(listColumns.RootNodeCount) + IndexCount + FForeignKeys.Count > 0 then - Delete(SQL, Length(SQL)-2, 3); - - SQL := SQL + CRLF + ')' + CRLF; - if memoComment.Text <> '' then - SQL := SQL + 'COMMENT='+DBObject.Connection.EscapeString(memoComment.Text) + CRLF; + if (memoComment.Text <> '') and (not DBObject.Connection.Parameters.IsAnyPostgreSQL) then + SQL := SQL + 'COMMENT='+DBObject.Connection.EscapeString(memoComment.Text) + sLineBreak; if comboCollation.Text <> '' then - SQL := SQL + 'COLLATE='+DBObject.Connection.EscapeString(comboCollation.Text) + CRLF; + SQL := SQL + 'COLLATE='+DBObject.Connection.EscapeString(comboCollation.Text) + sLineBreak; if (comboEngine.Text <> '') and (comboEngine.ItemIndex > 0) then begin if DBObject.Connection.ServerVersionInt < 40018 then - SQL := SQL + 'TYPE='+comboEngine.Text + CRLF + SQL := SQL + 'TYPE='+comboEngine.Text + sLineBreak else - SQL := SQL + 'ENGINE='+comboEngine.Text + CRLF; + SQL := SQL + 'ENGINE='+comboEngine.Text + sLineBreak; end; if comboRowFormat.Text <> 'DEFAULT' then - SQL := SQL + 'ROW_FORMAT='+comboRowFormat.Text + CRLF; + SQL := SQL + 'ROW_FORMAT='+comboRowFormat.Text + sLineBreak; if chkChecksum.Checked then - SQL := SQL + 'CHECKSUM='+IntToStr(Integer(chkChecksum.Checked)) + CRLF; + SQL := SQL + 'CHECKSUM='+IntToStr(Integer(chkChecksum.Checked)) + sLineBreak; if editAutoInc.Text <> '' then - SQL := SQL + 'AUTO_INCREMENT='+editAutoInc.Text + CRLF; + SQL := SQL + 'AUTO_INCREMENT='+editAutoInc.Text + sLineBreak; if editAvgRowLen.Text <> '' then - SQL := SQL + 'AVG_ROW_LENGTH='+editAvgRowLen.Text + CRLF; + SQL := SQL + 'AVG_ROW_LENGTH='+editAvgRowLen.Text + sLineBreak; if editMaxRows.Text <> '' then - SQL := SQL + 'MAX_ROWS='+editMaxRows.Text + CRLF; + SQL := SQL + 'MAX_ROWS='+editMaxRows.Text + sLineBreak; if memoUnionTables.Enabled and (memoUnionTables.Text <> '') then - SQL := SQL + 'UNION=('+memoUnionTables.Text+')' + CRLF; + SQL := SQL + 'UNION=('+memoUnionTables.Text+')' + sLineBreak; if comboInsertMethod.Enabled and (comboInsertMethod.Text <> '') then - SQL := SQL + 'INSERT_METHOD='+comboInsertMethod.Text + CRLF; + SQL := SQL + 'INSERT_METHOD='+comboInsertMethod.Text + sLineBreak; if SynMemoPartitions.GetTextLen > 0 then SQL := SQL + '/*!50100 ' + SynMemoPartitions.Text + ' */'; - SQL := SQL + ';' + CRLF; + SQL := SQL + ';' + sLineBreak; + + // Separate queries from here on if DBObject.Connection.Parameters.IsAnyPostgreSQL then begin - Node := listColumns.GetFirst; - while Assigned(Node) do begin - Col := listColumns.GetNodeData(Node); + if memoComment.Text <> '' then begin + SQL := SQL + 'COMMENT ON TABLE '+DBObject.Connection.QuoteIdent(editName.Text)+ + ' IS '+DBObject.Connection.EscapeString(memoComment.Text) + ';' + sLineBreak; + end; + for Col in FColumns do begin SQL := SQL + 'COMMENT ON COLUMN '+ DBObject.Connection.QuoteIdent(editName.Text)+'.'+DBObject.Connection.QuoteIdent(Col.Name)+ - ' IS '+DBObject.Connection.EscapeString(Col.Comment) + ';' + CRLF; - Node := listColumns.GetNextSibling(Node); + ' IS '+DBObject.Connection.EscapeString(Col.Comment) + ';' + sLineBreak; end; end; - Result := TSQLBatch.Create; + for i:=0 to FKeys.Count-1 do begin + if FKeys[i].InsideCreateCode then + Continue; + tmp := FKeys[i].SQLCode(editName.Text); + if tmp <> '' then begin + SQL := SQL + tmp + ';' + sLineBreak; + end; + end; + + Result := TSQLBatch.Create(DBObject.Connection.Parameters.NetTypeGroup); Result.SQL := Trim(SQL); end; @@ -919,6 +1076,43 @@ procedure TfrmTableEditor.Modification(Sender: TObject); end; end; +procedure TfrmTableEditor.ListViewReverseForeignKeysDblClick(Sender: TObject); +var + ClickItem: TListItem; + Obj: TDBObject; +begin + // Create virtual object and let mainform search for it in the tree + ClickItem := ListViewReverseForeignKeys.Selected; + if not Assigned(ClickItem) then + Exit; + Obj := TDBObject.Create(DBObject.Connection); + Obj.NodeType := lntTable; + Obj.Database := ClickItem.Caption; + Obj.Name := ClickItem.SubItems[0]; + MainForm.ActiveDbObj := Obj; +end; + +procedure TfrmTableEditor.btnShowReverseForeignKeysClick(Sender: TObject); +var + DoShow: Boolean; +begin + DoShow := btnShowReverseForeignKeys.Down; + if DoShow then begin + spltForeignKeyListings.Visible := True; + ListViewReverseForeignKeys.Visible := True; + spltForeignKeyListings.BringToFront; + spltForeignKeyListings.Left := ListViewReverseForeignKeys.Left - spltForeignKeyListings.Width; + ListViewReverseForeignKeys.BringToFront; + LoadReverseForeignKeys(Sender); + end + else begin + ListViewReverseForeignKeys.Visible := False; + spltForeignKeyListings.Visible := False; + listForeignKeys.Width := listForeignKeys.Parent.Width - tlbForeignKeys.Width; + end; + AppSettings.WriteBool(asDisplayReverseForeignKeys, DoShow); +end; + procedure TfrmTableEditor.btnAddColumnClick(Sender: TObject); var @@ -934,9 +1128,9 @@ procedure TfrmTableEditor.btnAddColumnClick(Sender: TObject); fn := listColumns.FocusedNode; NewCol := TTableColumn.Create(DBObject.Connection); if Assigned(fn) then begin - idx := fn.Index+1; // Copy properties from focused node FocusedCol := listColumns.GetNodeData(fn); + idx := FColumns.IndexOf(FocusedCol^) + 1; NewCol.DataType := FocusedCol.DataType; NewCol.LengthSet := FocusedCol.LengthSet; NewCol.Unsigned := FocusedCol.Unsigned; @@ -946,8 +1140,8 @@ procedure TfrmTableEditor.btnAddColumnClick(Sender: TObject); NewCol.DefaultType := cdtText; NewCol.DefaultText := '0'; end else begin - NewCol.DefaultType := FocusedCol.DefaultType; - NewCol.DefaultText := FocusedCol.DefaultText; + NewCol.DefaultType := cdtNothing; + NewCol.DefaultText := ''; end; NewCol.Collation := ''; end else begin @@ -958,9 +1152,9 @@ procedure TfrmTableEditor.btnAddColumnClick(Sender: TObject); NewCol.AllowNull := True; NewCol.DefaultType := cdtNothing; NewCol.DefaultText := ''; - NewCol.Comment := ''; - NewCol.Collation := ''; end; + NewCol.Comment := ''; + NewCol.Collation := ''; NewCol.Name := _('Column')+' '+IntToStr(idx+1); FColumns.Insert(idx, NewCol); NewNode := listColumns.InsertNode(fn, amInsertAfter, @NewCol); @@ -1014,13 +1208,22 @@ procedure TfrmTableEditor.btnRemoveColumnClick(Sender: TObject); procedure TfrmTableEditor.btnMoveUpColumnClick(Sender: TObject); var Node: PVirtualNode; + Col: PTableColumn; + ColId: NativeInt; begin // Move up selected columns listColumns.EndEditNode; Node := GetNextNode(listColumns, nil, true); while Assigned(Node) do begin + // Move column within FColumns list... + Col := listColumns.GetNodeData(Node); + ColId := FColumns.IndexOf(Col^); + FColumns.Move(ColId, ColId-1); + // ... and the tree node as well listColumns.MoveTo(Node, listColumns.GetPreviousSibling(Node), amInsertBefore, False); + Col.Status := esModified; + Modification(Sender); Node := GetNextNode(listColumns, Node, true); end; @@ -1031,6 +1234,8 @@ procedure TfrmTableEditor.btnMoveUpColumnClick(Sender: TObject); procedure TfrmTableEditor.btnMoveDownColumnClick(Sender: TObject); var Node: PVirtualNode; + Col: PTableColumn; + ColId: NativeInt; begin // Move down selected columns listColumns.EndEditNode; @@ -1038,7 +1243,12 @@ procedure TfrmTableEditor.btnMoveDownColumnClick(Sender: TObject); Node := listColumns.GetLast; while Assigned(Node) do begin if listColumns.Selected[Node] then begin + Col := listColumns.GetNodeData(Node); + ColId := FColumns.IndexOf(Col^); + FColumns.Move(ColId, ColId+1); listColumns.MoveTo(Node, listColumns.GetNextSibling(Node), amInsertAfter, False); + Col.Status := esModified; + Modification(Sender); end; Node := listColumns.GetPrevious(Node); end; @@ -1047,30 +1257,44 @@ procedure TfrmTableEditor.btnMoveDownColumnClick(Sender: TObject); end; +function TfrmTableEditor.MoveNodeAllowed(Sender: TVirtualStringTree): Boolean; +begin + // Allow moving nodes per button or per drag'n drop only if list is sorted by first column + Result := (Sender.Header.SortColumn = 0) + and (Sender.Header.SortDirection = sdAscending); +end; + procedure TfrmTableEditor.listColumnsDragOver(Sender: TBaseVirtualTree; Source: TObject; Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode; var Effect: Integer; var Accept: Boolean); begin - Accept := (Source = Sender) and (Mode <> dmNowhere); + Accept := (Source = Sender) and MoveNodeAllowed(listColumns) and (Mode <> dmNowhere); // Not sure what this effect does, probably show a specific mouse cursor? Effect := DROPEFFECT_MOVE; end; procedure TfrmTableEditor.listColumnsDragDrop(Sender: TBaseVirtualTree; - Source: TObject; DataObject: IDataObject; Formats: TFormatArray; + Source: TObject; DataObject: TVTDragDataObject; Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode); var - Node: PVirtualNode; - AttachMode: TVTNodeAttachMode; -begin - Node := Sender.GetNodeAt(Pt.X, Pt.Y); - if Assigned(Node) then begin - case Mode of - dmAbove, dmOnNode: AttachMode := amInsertBefore; - else AttachMode := amInsertAfter; - end; - listColumns.MoveTo(listColumns.FocusedNode, Node, AttachMode, False); + ToNode: PVirtualNode; + ToCol, FocusedCol: PTableColumn; + NewIndex: NativeInt; +begin + ToNode := Sender.GetNodeAt(Pt.X, Pt.Y); + if Assigned(ToNode) then begin + FocusedCol := Sender.GetNodeData(Sender.FocusedNode); + ToCol := Sender.GetNodeData(ToNode); + NewIndex := FColumns.IndexOf(ToCol^); + if Mode = dmBelow then + Inc(NewIndex); + // Fix crash when moving to very bottom + NewIndex := Min(NewIndex, FColumns.Count-1); + FColumns.Move(FColumns.IndexOf(FocusedCol^), NewIndex); + FocusedCol.Status := esModified; + Modification(Sender); + Sender.SortTree(Sender.Header.SortColumn, Sender.Header.SortDirection); ValidateColumnControls; end; end; @@ -1079,11 +1303,20 @@ procedure TfrmTableEditor.listColumnsDragDrop(Sender: TBaseVirtualTree; procedure TfrmTableEditor.listColumnsBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); +var + BgColor: TColor; begin + BgColor := MainForm.GetAlternatingRowBackground(Node); + // Darken cell background to signalize it doesn't allow length/set // Exclude non editable checkbox columns - grey looks ugly there. - if (not CellEditingAllowed(Node, Column)) and (not (Column in [4, 5, 6])) then begin - TargetCanvas.Brush.Color := GetThemeColor(clBtnFace); + if (not CellEditingAllowed(Node, Column)) and (Column <> ColNumCounter) then begin + BgColor := clBtnFace; + end; + + // Apply color + if BgColor <> clNone then begin + TargetCanvas.Brush.Color := BgColor; TargetCanvas.FillRect(CellRect); end; end; @@ -1145,7 +1378,7 @@ procedure TfrmTableEditor.listColumnsAfterCellPaint(Sender: TBaseVirtualTree; Y := CellRect.Top + Integer(VT.NodeHeight[Node] div 2) - (VT.Images.Height div 2); // Paint one icon per index type of which this column is part of - if Column = 0 then begin + if Column = ColNumCounter then begin X := 0; ImageIndexes := GetKeyImageIndexes(Col^); for i in ImageIndexes do begin @@ -1157,8 +1390,13 @@ procedure TfrmTableEditor.listColumnsAfterCellPaint(Sender: TBaseVirtualTree; // Paint checkbox image in certain columns // while restricting "Allow NULL" checkbox to numeric datatypes - if (Column in [4, 5, 6]) then begin - Checked := (Col.Unsigned and (Column=4)) or (Col.AllowNull and (Column=5)) or (Col.ZeroFill and (Column = 6)); + if (Column in ColNumsCheckboxes) then begin + Checked := (Col.Unsigned and (Column=ColNumUnsigned)) + or (Col.AllowNull and (Column=ColNumAllownull)) + or (Col.ZeroFill and (Column = ColNumZerofill)) + or (Col.Invisible and (Column = ColNumInvisible)) + or (Col.Compressed and (Column = ColNumCompressed)) + ; if CellEditingAllowed(Node, Column) then begin if Checked then ImageIndex := 128 else ImageIndex := 127; @@ -1195,14 +1433,17 @@ procedure TfrmTableEditor.ValidateColumnControls; btnMoveUpColumn.Enabled := (listColumns.SelectedCount > 0) and (listColumns.GetFirstSelected <> listColumns.GetFirst) - and (DBObject.Connection.Parameters.NetTypeGroup = ngMySQL); + and (DBObject.Connection.Parameters.NetTypeGroup = ngMySQL) + and MoveNodeAllowed(listColumns); btnMoveDownColumn.Enabled := (listColumns.SelectedCount > 0) and (LastSelected <> listColumns.GetLast) - and (DBObject.Connection.Parameters.NetTypeGroup = ngMySQL); + and (DBObject.Connection.Parameters.NetTypeGroup = ngMySQL) + and MoveNodeAllowed(listColumns); menuRemoveColumn.Enabled := btnRemoveColumn.Enabled; menuMoveUpColumn.Enabled := btnMoveUpColumn.Enabled; menuMoveDownColumn.Enabled := btnMoveDownColumn.Enabled; + listColumns.Invalidate; end; @@ -1210,7 +1451,7 @@ procedure TfrmTableEditor.listColumnsEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); begin // Allow text editing? Explicitely block that in checkbox columns - Allowed := CellEditingAllowed(Node, Column) and (not (Column in [4,5,6])); + Allowed := CellEditingAllowed(Node, Column) and (not (Column in ColNumsCheckboxes)); end; @@ -1222,9 +1463,11 @@ function TfrmTableEditor.CellEditingAllowed(Node: PVirtualNode; Column: TColumnI Col := listColumns.GetNodeData(Node); case Column of // No editor for very first column and checkbox columns - 0: Result := False; - 3: Result := Col.DataType.HasLength; - 4: begin + ColNumCounter: Result := False; + + ColNumLengthSet: Result := Col.DataType.HasLength; + + ColNumUnsigned: begin Result := (Col.DataType.Category in [dtcInteger, dtcReal]) and (Col.DataType.Index <> dbdtBit) and (DBObject.Connection.Parameters.IsAnyMySQL); @@ -1233,11 +1476,12 @@ function TfrmTableEditor.CellEditingAllowed(Node: PVirtualNode; Column: TColumnI Col.Status := esModified; end; end; - 5: begin + + ColNumAllownull: begin // Do not allow NULL, and force NOT NULL, on primary key columns Result := True; for i:=0 to FKeys.Count-1 do begin - if (FKeys[i].IndexType = TTableKey.PRIMARY) and (FKeys[i].Columns.IndexOf(Col.Name) > -1) then begin + if FKeys[i].IsPrimary and (FKeys[i].Columns.IndexOf(Col.Name) > -1) then begin if Col.AllowNull then begin Col.AllowNull := False; Col.Status := esModified; @@ -1247,7 +1491,8 @@ function TfrmTableEditor.CellEditingAllowed(Node: PVirtualNode; Column: TColumnI end; end; end; - 6: begin + + ColNumZerofill: begin Result := (Col.DataType.Category in [dtcInteger, dtcReal]) and (Col.DataType.Index <> dbdtBit) and (DBObject.Connection.Parameters.IsAnyMySQL); @@ -1256,15 +1501,23 @@ function TfrmTableEditor.CellEditingAllowed(Node: PVirtualNode; Column: TColumnI Col.Status := esModified; end; end; + // No editing of collation allowed if "Convert data" was checked - 9: Result := not chkCharsetConvert.Checked; + ColNumCollation: Result := not chkCharsetConvert.Checked; + + ColNumSrid: Result := (Col.DataType.Category = dtcSpatial) and DBObject.Connection.Has(frSrid); + + ColNumInvisible: Result := DBObject.Connection.Has(frInvisibleColumns); + + ColNumCompressed: Result := DBObject.Connection.Has(frCompressedColumns); + else Result := True; end; // SQLite does not support altering existing columns, except renaming. See issue #1256 if ObjectExists and DBObject.Connection.Parameters.IsAnySQLite then begin if Col.Status in [esUntouched, esModified, esDeleted] then begin - Result := Result and (Column = 1); + Result := Result and (Column = ColNumName); if (not Result) and (not FAlterRestrictedMessageDisplayed) then begin MainForm.LogSQL( f_('Altering tables restricted. For details see %s', ['https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes']), @@ -1287,18 +1540,27 @@ procedure TfrmTableEditor.listColumnsGetText(Sender: TBaseVirtualTree; Col := Sender.GetNodeData(Node); CellText := ''; case Column of - 0: CellText := IntToStr(Node.Index+1); - 1: CellText := Col.Name; - 2: CellText := Col.DataType.Name; - 3: CellText := Col.LengthSet; - 4, 5, 6: CellText := ''; // Checkbox - 7: begin + ColNumCounter: CellText := IntToStr(FColumns.IndexOf(Col^)+1); + + ColNumName: CellText := Col.Name; + + ColNumDatatype: CellText := Col.DataType.Name; + + ColNumLengthSet: CellText := Col.LengthSet; + + ColNumUnsigned: CellText := Col.Unsigned.ToInteger.ToString; + + ColNumAllownull: CellText := Col.AllowNull.ToInteger.ToString; + + ColNumZerofill: CellText := Col.ZeroFill.ToInteger.ToString; + + ColNumDefault: begin case Col.DefaultType of cdtNothing: CellText := _('No default'); cdtText: CellText := Col.Connection.EscapeString(Col.DefaultText); cdtNull: CellText := 'NULL'; cdtExpression: CellText := Col.DefaultText; - cdtAutoInc: CellText := Col.AutoIncName; + cdtAutoInc: CellText := Col.Connection.SqlProvider.GetSql(qAutoInc); end; case Col.OnUpdateType of // cdtNothing: leave clause away @@ -1308,14 +1570,28 @@ procedure TfrmTableEditor.listColumnsGetText(Sender: TBaseVirtualTree; // cdtAutoInc: invalid here end; end; - 8: CellText := Col.Comment; - 9: begin + + ColNumComment: CellText := Col.Comment; + + ColNumCollation: begin CellText := Col.Collation; if (CellText <> '') and (chkCharsetConvert.Checked) then CellText := comboCollation.Text; end; - 10: CellText := Col.GenerationExpression; - 11: CellText := Col.Virtuality; + + ColNumExpression: CellText := Col.GenerationExpression; + + ColNumVirtuality: CellText := Col.Virtuality; + + ColNumSrid: begin + if (Col.DataType.Category = dtcSpatial) and (Col.Connection.Has(frSrid)) then + CellText := Col.SRID.ToString; + end; + + ColNumInvisible: CellText := Col.Invisible.ToInteger.ToString; + + ColNumCompressed: CellText := Col.Compressed.ToInteger.ToString; + end; end; @@ -1341,14 +1617,13 @@ procedure TfrmTableEditor.listColumnsPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); var - TextColor: TColor; i: Integer; Col: PTableColumn; begin Col := Sender.GetNodeData(Node); // Bold font for primary key columns for i:=0 to FKeys.Count-1 do begin - if (FKeys[i].IndexType = TTableKey.PRIMARY) and (FKeys[i].Columns.IndexOf(Col.Name) > -1) then begin + if FKeys[i].IsPrimary and (FKeys[i].Columns.IndexOf(Col.Name) > -1) then begin TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold]; break; end; @@ -1359,20 +1634,19 @@ procedure TfrmTableEditor.listColumnsPaintText(Sender: TBaseVirtualTree; //if vsSelected in Node.States then Exit; // Give datatype column specific color, as set in preferences - TextColor := TargetCanvas.Font.Color; case Column of - 0: TextColor := clGrayText; + ColNumCounter: TargetCanvas.Font.Color := clGrayText; - 2: TextColor := DatatypeCategories[Col.DataType.Category].Color; + ColNumDatatype: TargetCanvas.Font.Color := DatatypeCategories[Col.DataType.Category].Color; - 7: case Col.DefaultType of + ColNumDefault: case Col.DefaultType of cdtNothing, cdtNull: - TextColor := DatatypeCategories[Col.DataType.Category].NullColor; + TargetCanvas.Font.Color := DatatypeCategories[Col.DataType.Category].NullColor; else - TextColor := DatatypeCategories[Col.DataType.Category].Color; + TargetCanvas.Font.Color := DatatypeCategories[Col.DataType.Category].Color; end; end; - TargetCanvas.Font.Color := TextColor; + end; @@ -1383,12 +1657,13 @@ procedure TfrmTableEditor.listColumnsNewText(Sender: TBaseVirtualTree; Col: PTableColumn; Key: TTableKey; WasModified: Boolean; + OldDatatype: TDBDatatype; begin // Column property edited Col := Sender.GetNodeData(Node); WasModified := True; case Column of - 1: begin // Name of column + ColNumName: begin for i:=0 to FColumns.Count-1 do begin if (FColumns[i].Name = NewText) and (not (FColumns[i].Status in [esDeleted, esAddedDeleted])) then begin ErrorDialog(f_('Column "%s" already exists.', [NewText])); @@ -1404,49 +1679,63 @@ procedure TfrmTableEditor.listColumnsNewText(Sender: TBaseVirtualTree; treeIndexes.Invalidate; Col.Name := NewText; end; - 2: begin // Data type + + ColNumDatatype: begin + OldDatatype := Col.DataType; Col.DataType := DBObject.Connection.GetDatatypeByName(NewText, False, Col.Name); // Reset length/set for column types which don't support that if not Col.DataType.HasLength then Col.LengthSet := ''; + // Remove subpart from indexes where this column is a part of + if Col.DataType.Category <> dtcText then begin + for Key in FKeys do begin + for i:=0 to Key.Columns.Count-1 do begin + if Key.Columns[i] = Col.Name then + Key.SubParts[i] := ''; + end; + end; + treeIndexes.Invalidate; + end; // Suggest length/set if required if (not Col.LengthCustomized) or (Col.DataType.RequiresLength and (Col.LengthSet = '')) then Col.LengthSet := Col.DataType.DefLengthSet; // Auto-change default type and text - if not Col.DataType.HasDefault then begin - Col.DefaultType := cdtNothing; - Col.DefaultText := ''; - end else begin - // Auto-fix user selected default type which can be invalid now - case Col.DataType.Category of - dtcInteger: begin - Col.DefaultType := cdtExpression; - if Col.AllowNull then - Col.DefaultType := cdtNull - else - Col.DefaultText := IntToStr(MakeInt(Col.DefaultText)); - end; - dtcReal: begin - Col.DefaultType := cdtExpression; - if Col.AllowNull then - Col.DefaultType := cdtNull - else - Col.DefaultText := FloatToStr(MakeFloat(Col.DefaultText)); - end; - dtcText, dtcBinary, dtcSpatial, dtcOther: begin - Col.DefaultType := cdtText; - if Col.AllowNull then - Col.DefaultType := cdtNull; - end; - dtcTemporal: begin - if Col.DefaultType = cdtAutoinc then - Col.DefaultType := cdtNothing; + if Col.DataType.Category <> OldDatatype.Category then begin + if not Col.DataType.HasDefault then begin + Col.DefaultType := cdtNothing; + Col.DefaultText := ''; + end else begin + // Auto-fix user selected default type which can be invalid now + case Col.DataType.Category of + dtcInteger: begin + Col.DefaultType := cdtExpression; + if Col.AllowNull then + Col.DefaultType := cdtNull + else + Col.DefaultText := IntToStr(MakeInt(Col.DefaultText)); + end; + dtcReal: begin + Col.DefaultType := cdtExpression; + if Col.AllowNull then + Col.DefaultType := cdtNull + else + Col.DefaultText := FloatToStr(MakeFloat(Col.DefaultText)); + end; + dtcText, dtcBinary, dtcSpatial, dtcOther: begin + Col.DefaultType := cdtText; + if Col.AllowNull then + Col.DefaultType := cdtNull; + end; + dtcTemporal: begin + if Col.DefaultType = cdtAutoinc then + Col.DefaultType := cdtNothing; + end; end; end; end; + end; - end; // Length / Set - 3: begin + ColNumLengthSet: begin if Col.DataType.RequiresLength and (NewText='') then begin WasModified := False; ErrorDialog(f_('Column data type %s requires a length/set', [Col.DataType.Name])); @@ -1455,16 +1744,24 @@ procedure TfrmTableEditor.listColumnsNewText(Sender: TBaseVirtualTree; Col.LengthCustomized := True; end; end; - // 4 + 5 are checkboxes - handled in OnClick - 7: begin // Default value + + // 4, 5, 6, 13 are checkboxes - handled in OnClick + + ColNumDefault: begin // DefaultText/Type and OnUpdateText/Type are set in TColumnDefaultEditorLink.EndEdit if Col.DefaultType = cdtNull then Col.AllowNull := True; end; - 8: Col.Comment := NewText; - 9: Col.Collation := NewText; - 10: Col.GenerationExpression := NewText; - 11: Col.Virtuality := NewText; + + ColNumComment: Col.Comment := NewText; + + ColNumCollation: Col.Collation := NewText; + + ColNumExpression: Col.GenerationExpression := NewText; + + ColNumVirtuality: Col.Virtuality := NewText; + + ColNumSrid: Col.SRID := StrToUIntDef(NewText, 0); end; if WasModified then begin Col.Status := esModified; @@ -1473,16 +1770,6 @@ procedure TfrmTableEditor.listColumnsNewText(Sender: TBaseVirtualTree; end; -procedure TfrmTableEditor.listColumnsNodeMoved(Sender: TBaseVirtualTree; Node: PVirtualNode); -var - Col: PTableColumn; -begin - Col := Sender.GetNodeData(Node); - Col.Status := esModified; - Modification(Sender); -end; - - procedure TfrmTableEditor.listColumnsChange(Sender: TBaseVirtualTree; Node: PVirtualNode); begin @@ -1510,7 +1797,7 @@ procedure TfrmTableEditor.listColumnsKeyPress(Sender: TObject; var Key: Char); begin // Space/click on checkbox column VT := Sender as TVirtualStringTree; - if (Ord(Key) = VK_SPACE) and (VT.FocusedColumn in [4, 5, 6]) then + if (Ord(Key) = VK_SPACE) and (VT.FocusedColumn in ColNumsCheckboxes) then vtHandleClickOrKeyPress(VT, VT.FocusedNode, VT.FocusedColumn, []); end; @@ -1528,13 +1815,14 @@ procedure TfrmTableEditor.vtHandleClickOrKeyPress(Sender: TVirtualStringTree; if CellEditingAllowed(Node, Column) then begin Col := VT.GetNodeData(Node); case Column of - 4: begin + ColNumUnsigned: begin Col.Unsigned := not Col.Unsigned; Col.Status := esModified; Modification(Sender); VT.InvalidateNode(Node); end; - 5: begin + + ColNumAllownull: begin Col.AllowNull := not Col.AllowNull; // Switch default value from NULL to Text if Allow Null is off if (not Col.AllowNull) and (Col.DefaultType = cdtNull) then begin @@ -1545,12 +1833,28 @@ procedure TfrmTableEditor.vtHandleClickOrKeyPress(Sender: TVirtualStringTree; Modification(Sender); VT.InvalidateNode(Node); end; - 6: begin + + ColNumZerofill: begin Col.ZeroFill := not Col.ZeroFill; Col.Status := esModified; Modification(Sender); VT.InvalidateNode(Node); end; + + ColNumInvisible: begin + Col.Invisible := not Col.Invisible; + Col.Status := esModified; + Modification(Sender); + VT.InvalidateNode(Node); + end; + + ColNumCompressed: begin + Col.Compressed := not Col.Compressed; + Col.Status := esModified; + Modification(Sender); + VT.InvalidateNode(Node); + end; + else begin // All other cells go into edit mode please // Explicitely done on OnClick, not in OnFocusChanged which seemed annoying for keyboard users @@ -1575,11 +1879,23 @@ procedure TfrmTableEditor.listColumnsCreateEditor(Sender: TBaseVirtualTree; VT := Sender as TVirtualStringTree; Col := Sender.GetNodeData(Node); case Column of - 2: begin // Datatype pulldown + ColNumDatatype: begin // Datatype pulldown DatatypeEditor := TDatatypeEditorLink.Create(VT, True, Col^); EditLink := DataTypeEditor; end; - 9: begin // Collation pulldown + + ColNumLengthSet: begin + if DBObject.Connection.Parameters.IsAnyPostgreSQL and (Col.DataType.Index = dbdtEnum) then begin + EnumEditor := TEnumEditorLink.Create(VT, True, Col^); + EnumEditor.AllowCustomText := True; + EnumEditor.ItemMustExist := False; + EnumEditor.ValueList := Explode('|', Col.DataType.Names); + EnumEditor.ValueList.Insert(0, ''); + EditLink := EnumEditor; + end; + end; + + ColNumCollation: begin // Collation pulldown EnumEditor := TEnumEditorLink.Create(VT, True, Col^); EnumEditor.AllowCustomText := True; EnumEditor.ItemMustExist := True; @@ -1589,7 +1905,8 @@ procedure TfrmTableEditor.listColumnsCreateEditor(Sender: TBaseVirtualTree; EnumEditor.ValueList.Insert(0, ''); EditLink := EnumEditor; end; - 7: begin + + ColNumDefault: begin DefaultEditor := TColumnDefaultEditorLink.Create(VT, True, Col^); DefaultEditor.DefaultType := Col.DefaultType; DefaultEditor.DefaultText := Col.DefaultText; @@ -1597,7 +1914,8 @@ procedure TfrmTableEditor.listColumnsCreateEditor(Sender: TBaseVirtualTree; DefaultEditor.OnUpdateText := Col.OnUpdateText; EditLink := DefaultEditor; end; - 11: begin // Virtuality pulldown + + ColNumVirtuality: begin // Virtuality pulldown EnumEditor := TEnumEditorLink.Create(VT, True, Col^); EnumEditor.ValueList := TStringList.Create; if DBObject.Connection.Parameters.IsMariaDB then @@ -1606,12 +1924,14 @@ procedure TfrmTableEditor.listColumnsCreateEditor(Sender: TBaseVirtualTree; EnumEditor.ValueList.CommaText := ',VIRTUAL,STORED'; EditLink := EnumEditor; end - else begin - Edit := TInplaceEditorLink.Create(VT, True, Col^); - Edit.TitleText := VT.Header.Columns[Column].Text; - Edit.ButtonVisible := True; - EditLink := Edit; - end; + + end; + + if (not Assigned(EditLink)) then begin + Edit := TInplaceEditorLink.Create(VT, True, Col^); + Edit.TitleText := VT.Header.Columns[Column].Text; + Edit.ButtonVisible := True; + EditLink := Edit; end; end; @@ -1697,7 +2017,7 @@ procedure TfrmTableEditor.menuAddIndexColumnClick(Sender: TObject); end; if not ColExists then begin NewCol := Column.Name; - if (TblKey.IndexType <> TTableKey.FULLTEXT) and (Column.DataType.Index in [dbdtTinyText, dbdtText, dbdtMediumText, dbdtLongText, dbdtTinyBlob, dbdtBlob, dbdtMediumBlob, dbdtLongBlob]) then + if (not TblKey.IsFulltext) and (Column.DataType.Index in [dbdtTinyText, dbdtText, dbdtMediumText, dbdtLongText, dbdtTinyBlob, dbdtBlob, dbdtMediumBlob, dbdtLongBlob]) then PartLength := '100'; break; end; @@ -1705,6 +2025,7 @@ procedure TfrmTableEditor.menuAddIndexColumnClick(Sender: TObject); treeIndexes.AddChild(Node); TblKey.Columns.Add(NewCol); TblKey.SubParts.Add(PartLength); + TblKey.Collations.Add('A'); Modification(Sender); treeIndexes.Invalidate; SelectNode(treeIndexes, FKeys.Count-1, Node); @@ -1715,6 +2036,7 @@ procedure TfrmTableEditor.btnRemoveIndexClick(Sender: TObject); var idx: Integer; NewSelectNode: PVirtualNode; + DeleteTblKey: TTableKey; begin // Remove index or part if treeIndexes.IsEditing then @@ -1722,8 +2044,11 @@ procedure TfrmTableEditor.btnRemoveIndexClick(Sender: TObject); case treeIndexes.GetNodeLevel(treeIndexes.FocusedNode) of 0: begin idx := treeIndexes.FocusedNode.Index; - if not FKeys[idx].Added then - FDeletedKeys.Add(FKeys[idx].OldName); + if not FKeys[idx].Added then begin + DeleteTblKey := TTableKey.Create(DBObject.Connection); + DeleteTblKey.Assign(FKeys[idx]); + FDeletedKeys.Add(DeleteTblKey); + end; FKeys.Delete(idx); // Delete node although ReinitChildren would do the same, but the Repaint before // creates AVs in certain cases. See issue #2557 @@ -1733,6 +2058,7 @@ procedure TfrmTableEditor.btnRemoveIndexClick(Sender: TObject); idx := treeIndexes.FocusedNode.Parent.Index; FKeys[idx].Columns.Delete(treeIndexes.FocusedNode.Index); FKeys[idx].SubParts.Delete(treeIndexes.FocusedNode.Index); + FKeys[idx].Collations.Delete(treeIndexes.FocusedNode.Index); treeIndexes.DeleteNode(treeIndexes.FocusedNode); end; end; @@ -1747,7 +2073,7 @@ procedure TfrmTableEditor.btnRemoveIndexClick(Sender: TObject); procedure TfrmTableEditor.btnClearIndexesClick(Sender: TObject); var - TblKey: TTableKey; + TblKey, DeleteTblKey: TTableKey; begin // Clear all indexes // Column data gets freed below - end any editor which could cause AV's @@ -1756,8 +2082,11 @@ procedure TfrmTableEditor.btnClearIndexesClick(Sender: TObject); // Trigger ValidateIndexControls SelectNode(treeIndexes, nil); for TblKey in FKeys do begin - if not TblKey.Added then - FDeletedKeys.Add(TblKey.OldName); + if not TblKey.Added then begin + DeleteTblKey := TTableKey.Create(DBObject.Connection); + DeleteTblKey.Assign(TblKey); + FDeletedKeys.Add(DeleteTblKey); + end; end; FKeys.Clear; Modification(Sender); @@ -1770,14 +2099,26 @@ procedure TfrmTableEditor.treeIndexesGetImageIndex(Sender: TBaseVirtualTree; var Ghosted: Boolean; var ImageIndex: TImageIndex); var VT: TVirtualStringTree; + TblKey: TTableKey; begin // Icon image showing type of index VT := Sender as TVirtualStringTree; - if Column <> 0 then Exit; - if not (Kind in [ikNormal, ikSelected]) then Exit; + if Column <> IndexColNumName then + Exit; + if not (Kind in [ikNormal, ikSelected]) then + Exit; case VT.GetNodeLevel(Node) of - 0: ImageIndex := FKeys[Node.Index].ImageIndex; - 1: ImageIndex := 42; + 0: begin + ImageIndex := FKeys[Node.Index].ImageIndex; + Ghosted := not FKeys[Node.Index].Visible; + end; + 1: begin + TblKey := FKeys[Node.Parent.Index]; + if TblKey.IsExpression(Node.Index) then + ImageIndex := 13 + else + ImageIndex := 42; + end; end; end; @@ -1793,21 +2134,33 @@ procedure TfrmTableEditor.treeIndexesGetText(Sender: TBaseVirtualTree; 0: begin TblKey := FKeys[Node.Index]; case Column of - 0: if TblKey.IndexType = TTableKey.PRIMARY then + IndexColNumName: if TblKey.IsPrimary then CellText := TblKey.IndexType + ' KEY' // Fixed name "PRIMARY KEY", cannot be changed else CellText := TblKey.Name; - 1: CellText := TblKey.IndexType; - 2: CellText := TblKey.Algorithm; - 3: CellText := TblKey.Comment; + IndexColNumType: CellText := TblKey.IndexType; + IndexColNumAlgorithm: CellText := TblKey.Algorithm; + IndexColNumComment: CellText := TblKey.Comment; + IndexColNumDirection: CellText := ''; // Column collation + IndexColNumVisibility: CellText := IfThen( + TblKey.Visible, + DBObject.Connection.SqlProvider.GetSql(qIndexVisible), + DBObject.Connection.SqlProvider.GetSql(qIndexInvisible) + ); end; end; 1: begin TblKey := FKeys[Node.Parent.Index]; case Column of - 0: CellText := TblKey.Columns[Node.Index]; - 1: CellText := TblKey.SubParts[Node.Index]; - 2: CellText := ''; + IndexColNumName: CellText := TblKey.Columns[Node.Index]; + IndexColNumType: CellText := TblKey.SubParts[Node.Index]; + IndexColNumAlgorithm: CellText := ''; // Index algorithm + IndexColNumComment: CellText := ''; // Index comment + IndexColNumDirection: begin + CellText := TblKey.Collations[Node.Index]; + CellText := IfThen(CellText.ToLower = 'a', 'ASC', 'DESC'); + end; + IndexColNumVisibility: CellText := ''; end; end; end; @@ -2025,21 +2378,28 @@ procedure TfrmTableEditor.treeIndexesEditing(Sender: TBaseVirtualTree; VT := Sender as TVirtualStringtree; Allowed := False; if VT.GetNodeLevel(Node) = 0 then begin - // Disallow renaming primary key - if (Column <> 0) or (VT.Text[Node, 1] <> TTableKey.PRIMARY) then - Allowed := True - end else case Column of - 0: Allowed := True; - 1: begin + // Disallow renaming primary key, and direction/collation of key node level + case Column of + IndexColNumName: Allowed := (VT.Text[Node, 1] <> TTableKey.PRIMARY); + IndexColNumType: Allowed := True; + IndexColNumAlgorithm: Allowed := True; + IndexColNumComment: Allowed := True; + IndexColNumVisibility: Allowed := DBObject.Connection.SqlProvider.Has(qIndexInvisible); + end; + end + else case Column of + IndexColNumName: Allowed := True; + IndexColNumType: begin // Column length is allowed for (var)char/text types only, even mandantory for text and blobs IndexedColName := VT.Text[Node, 0]; for i:=0 to FColumns.Count-1 do begin if FColumns[i].Name = IndexedColName then begin - Allowed := FColumns[i].DataType.Category = dtcText; + Allowed := FColumns[i].DataType.Category in [dtcText, dtcBinary]; break; end; end; end; + IndexColNumDirection: Allowed := True; // Collation end; end; @@ -2056,18 +2416,25 @@ procedure TfrmTableEditor.treeIndexesCreateEditor(Sender: TBaseVirtualTree; // Start cell editor VT := Sender as TVirtualStringTree; Level := (Sender as TVirtualStringtree).GetNodeLevel(Node); - if (Level = 0) and (Column = 1) then begin + if (Level = 0) and (Column = IndexColNumType) then begin // Index type pulldown EnumEditor := TEnumEditorLink.Create(VT, True, nil); EnumEditor.ValueList := TStringList.Create; EnumEditor.ValueList.CommaText := TTableKey.PRIMARY +','+ TTableKey.KEY +','+ TTableKey.UNIQUE +','+ TTableKey.FULLTEXT +','+ TTableKey.SPATIAL; EditLink := EnumEditor; - end else if (Level = 0) and (Column = 2) then begin + end else if (Level = 0) and (Column = IndexColNumAlgorithm) then begin // Algorithm pulldown EnumEditor := TEnumEditorLink.Create(VT, True, nil); EnumEditor.ValueList := Explode(',', ',BTREE,HASH,RTREE'); EditLink := EnumEditor; - end else if (Level = 1) and (Column = 0) then begin + end else if (Level = 0) and (Column = IndexColNumVisibility) then begin + // Visibility pulldown + EnumEditor := TEnumEditorLink.Create(VT, True, nil); + EnumEditor.ValueList.Add(''); + EnumEditor.ValueList.Add(DBObject.Connection.SqlProvider.GetSql(qIndexVisible)); + EnumEditor.ValueList.Add(DBObject.Connection.SqlProvider.GetSql(qIndexInvisible)); + EditLink := EnumEditor; + end else if (Level = 1) and (Column = IndexColNumName) then begin // Column names pulldown EnumEditor := TEnumEditorLink.Create(VT, True, nil); ColNode := listColumns.GetFirst; @@ -2078,6 +2445,10 @@ procedure TfrmTableEditor.treeIndexesCreateEditor(Sender: TBaseVirtualTree; end; EnumEditor.AllowCustomText := True; // Allows adding a subpart in index parts: "TextCol(20)" EditLink := EnumEditor; + end else if (Level = 1) and (Column = IndexColNumDirection) then begin + EnumEditor := TEnumEditorLink.Create(VT, True, nil); + EnumEditor.ValueList := Explode(',', ',ASC,DESC'); + EditLink := EnumEditor; end else EditLink := TInplaceEditorLink.Create(VT, True, nil); end; @@ -2096,14 +2467,15 @@ procedure TfrmTableEditor.treeIndexesNewText(Sender: TBaseVirtualTree; 0: begin TblKey := FKeys[Node.Index]; case Column of - 0: TblKey.Name := NewText; - 1: begin + IndexColNumName: TblKey.Name := NewText; + IndexColNumType: begin TblKey.IndexType := NewText; if NewText = TTableKey.PRIMARY then TblKey.Name := TTableKey.PRIMARY; end; - 2: TblKey.Algorithm := NewText; - 3: TblKey.Comment := NewText; + IndexColNumAlgorithm: TblKey.Algorithm := NewText; + IndexColNumComment: TblKey.Comment := NewText; + IndexColNumVisibility: TblKey.Visible := SameText(NewText, DBObject.Connection.SqlProvider.GetSql(qIndexVisible)); end; // Needs to be called manually for Name and IndexType properties: TblKey.Modification(Sender); @@ -2111,7 +2483,7 @@ procedure TfrmTableEditor.treeIndexesNewText(Sender: TBaseVirtualTree; 1: begin TblKey := FKeys[Node.Parent.Index]; case Column of - 0: begin + IndexColNumName: begin // Detect input of "col(123)" and move "123" into subpart rx := TRegExpr.Create; rx.Expression := '.+\((\d+)\)'; @@ -2121,7 +2493,13 @@ procedure TfrmTableEditor.treeIndexesNewText(Sender: TBaseVirtualTree; end else TblKey.Columns[Node.Index] := NewText; end; - 1: TblKey.SubParts[Node.Index] := NewText; + IndexColNumType: TblKey.SubParts[Node.Index] := NewText; + IndexColNumDirection: begin + if NewText.ToLower = 'asc' then + TblKey.Collations[Node.Index] := 'A' + else + TblKey.Collations[Node.Index] := 'D'; + end; end; TblKey.Modified := True; end; @@ -2163,7 +2541,7 @@ procedure TfrmTableEditor.treeIndexesDragOver(Sender: TBaseVirtualTree; procedure TfrmTableEditor.treeIndexesDragDrop(Sender: TBaseVirtualTree; - Source: TObject; DataObject: IDataObject; Formats: TFormatArray; + Source: TObject; DataObject: TVTDragDataObject; Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode); var FocusedNode, TargetNode, IndexNode: PVirtualNode; @@ -2229,9 +2607,10 @@ procedure TfrmTableEditor.treeIndexesDragDrop(Sender: TBaseVirtualTree; TblKey.Columns.Insert(ColPos, ColName); PartLength := ''; - if (TblKey.IndexType <> TTableKey.FULLTEXT) and (Col.DataType.Index in [dbdtTinyText, dbdtText, dbdtMediumText, dbdtLongText, dbdtTinyBlob, dbdtBlob, dbdtMediumBlob, dbdtLongBlob]) then + if (not TblKey.IsFulltext) and (Col.DataType.Index in [dbdtTinyText, dbdtText, dbdtMediumText, dbdtLongText, dbdtTinyBlob, dbdtBlob, dbdtMediumBlob, dbdtLongBlob]) then PartLength := '100'; TblKey.Subparts.Insert(ColPos, PartLength); + TblKey.Collations.Insert(ColPos, 'A'); IndexNode.States := IndexNode.States + [vsHasChildren, vsExpanded]; end; Modification(Sender); @@ -2269,6 +2648,7 @@ procedure TfrmTableEditor.MoveFocusedIndexPart(NewIdx: Cardinal); end; TblKey.Columns.Move(treeIndexes.FocusedNode.Index, NewIdx); TblKey.SubParts.Move(treeIndexes.FocusedNode.Index, NewIdx); + TblKey.Collations.Move(treeIndexes.FocusedNode.Index, NewIdx); Modification(treeIndexes); SelectNode(treeIndexes, NewIdx, treeIndexes.FocusedNode.Parent); end; @@ -2280,7 +2660,10 @@ procedure TfrmTableEditor.PageControlMainChange(Sender: TObject); listForeignKeys.EndEditNode; listCheckConstraints.EndEditNode; // Ensure SynMemo's have focus, otherwise Select-All and Copy actions may fail - if PageControlMain.ActivePage = tabCREATEcode then begin + if PageControlMain.ActivePage = tabForeignKeys then begin + LoadReverseForeignKeys(Sender); + end + else if PageControlMain.ActivePage = tabCREATEcode then begin SynMemoCreateCode.TrySetFocus; end else if PageControlMain.ActivePage = tabALTERcode then begin @@ -2301,7 +2684,7 @@ procedure TfrmTableEditor.UpdateSQLcode; OldTopLine := SynMemoALTERcode.TopLine; SynMemoALTERcode.Clear; for Query in ComposeAlterStatement do - SynMemoALTERcode.Text := SynMemoALTERcode.Text + Query.SQL + ';' + CRLF; + SynMemoALTERcode.Text := SynMemoALTERcode.Text + Query.SQL + ';' + sLineBreak; SynMemoALTERcode.TopLine := OldTopLine; SynMemoALTERcode.EndUpdate; AlterCodeValid := True; @@ -2310,7 +2693,7 @@ procedure TfrmTableEditor.UpdateSQLcode; OldTopLine := SynMemoCREATEcode.TopLine; SynMemoCREATEcode.Clear; for Query in ComposeCreateStatement do - SynMemoCREATEcode.Text := SynMemoCREATEcode.Text + Query.SQL + ';' + CRLF; + SynMemoCREATEcode.Text := SynMemoCREATEcode.Text + Query.SQL + ';' + sLineBreak; SynMemoCREATEcode.TopLine := OldTopLine; SynMemoCREATEcode.EndUpdate; CreateCodeValid := True; @@ -2357,7 +2740,7 @@ procedure TfrmTableEditor.popupColumnsPopup(Sender: TObject); // Auto create submenu items for "Add to index" ... PrimaryKeyExists := False; for i:=0 to FKeys.Count-1 do begin - if FKeys[i].IndexType = TTableKey.PRIMARY then begin + if FKeys[i].IsPrimary then begin PrimaryKeyExists := True; IndexName := TTableKey.PRIMARY; end else @@ -2486,8 +2869,10 @@ procedure TfrmTableEditor.AddIndexByColumn(Sender: TObject); TblKey.IndexType := NewType; TblKey.Added := True; TblKey.Columns := NewParts; - for i:=0 to TblKey.Columns.Count do + for i:=0 to TblKey.Columns.Count-1 do begin TblKey.SubParts.Add(''); + TblKey.Collations.Add('A'); + end; FKeys.Add(TblKey); PageControlMain.ActivePage := tabIndexes; treeIndexes.Repaint; @@ -2500,6 +2885,7 @@ procedure TfrmTableEditor.AddIndexByColumn(Sender: TObject); if TblKey.Columns.IndexOf(NewParts[i]) = -1 then begin TblKey.Columns.Add(NewParts[i]); TblKey.Subparts.Add(''); + TblKey.Collations.Add('A'); end; end; SelectNode(treeIndexes, Item.MenuIndex); @@ -2646,7 +3032,7 @@ procedure TfrmTableEditor.listForeignKeysCreateEditor( end; 4, 5: begin EnumEditor := TEnumEditorLink.Create(VT, True, nil); - EnumEditor.ValueList := Explode(',', DBObject.Connection.GetSQLSpecifity(spForeignKeyEventAction)); + EnumEditor.ValueList := Explode(',', DBObject.Connection.SqlProvider.GetSql(qForeignKeyEventAction)); EditLink := EnumEditor; end; end; @@ -2723,7 +3109,7 @@ procedure TfrmTableEditor.listForeignKeysNewText(Sender: TBaseVirtualTree; 2: begin Key.ReferenceTable := NewText; if not Key.KeyNameWasCustomized then begin - Key.KeyName := 'FK_'+DBObject.Name+'_'+Key.ReferenceTable; + Key.KeyName := 'FK_'+editName.Text+'_'+Key.ReferenceTable; i := 1; NameInUse := True; while NameInUse do begin @@ -2733,7 +3119,7 @@ procedure TfrmTableEditor.listForeignKeysNewText(Sender: TBaseVirtualTree; end; if NameInUse then begin Inc(i); - Key.KeyName := 'FK_'+DBObject.Name+'_'+Key.ReferenceTable+'_'+IntToStr(i); + Key.KeyName := 'FK_'+editName.Text+'_'+Key.ReferenceTable+'_'+IntToStr(i); end; end; @@ -2765,10 +3151,10 @@ procedure TfrmTableEditor.listForeignKeysNewText(Sender: TBaseVirtualTree; for i:=0 to Key.Columns.Count-1 do begin for j:=0 to FColumns.Count-1 do begin if FColumns[j].Name = Key.Columns[i] then begin - KeyColumnsSQLCode := KeyColumnsSQLCode + FColumns[j].SQLCode + CRLF; + KeyColumnsSQLCode := KeyColumnsSQLCode + FColumns[j].SQLCode + sLineBreak; for k:=0 to RefColumns.Count-1 do begin if RefColumns[k].Name = Key.ForeignColumns[i] then begin - RefColumnsSQLCode := RefColumnsSQLCode + RefColumns[k].SQLCode + CRLF; + RefColumnsSQLCode := RefColumnsSQLCode + RefColumns[k].SQLCode + sLineBreak; TypesMatch := TypesMatch and (RefColumns[k].DataType.Index = FColumns[j].DataType.Index) and (RefColumns[k].Unsigned = FColumns[j].Unsigned); @@ -2779,7 +3165,7 @@ procedure TfrmTableEditor.listForeignKeysNewText(Sender: TBaseVirtualTree; end; if not TypesMatch then begin Err := _('The selected foreign column do not match the source columns data type and unsigned flag. This will give you an error message when trying to save this change. Please compare yourself:'); - Err := Err + CRLF + CRLF + KeyColumnsSQLCode + CRLF + Trim(RefColumnsSQLCode); + Err := Err + sLineBreak + sLineBreak + KeyColumnsSQLCode + sLineBreak + Trim(RefColumnsSQLCode); end; end; if Err <> '' then @@ -2791,6 +3177,40 @@ procedure TfrmTableEditor.listForeignKeysNewText(Sender: TBaseVirtualTree; end; end; +procedure TfrmTableEditor.LoadReverseForeignKeys(Sender: TObject); +var + SqlGet: String; + Results: TDBQuery; + ListItem: TListItem; +begin + if FReverseForeignKeysLoaded then + Exit; + if not ListViewReverseForeignKeys.Visible then + Exit; + if not ObjectExists then // Jump out early when creating a new table + Exit; + SqlGet := DBObject.Connection.SqlProvider.GetSql(qGetReverseForeignKeys, DBObject.AsStringMap); + if SqlGet.IsEmpty then begin + MainForm.LogSQL(_('Database does not provide reverse foreign key listing')); + Exit; + end; + ListViewReverseForeignKeys.Items.BeginUpdate; + ListViewReverseForeignKeys.Clear; + try + Results := DBObject.Connection.GetResults(SqlGet); + while not Results.Eof do begin + ListItem := ListViewReverseForeignKeys.Items.Add; + ListItem.ImageIndex := ICONINDEX_TABLE; + ListItem.Caption := Results.Col(0); + ListItem.SubItems.Add(Results.Col(1)); + Results.Next; + end; + except + on EDbError do; + end; + ListViewReverseForeignKeys.Items.EndUpdate; + FReverseForeignKeysLoaded := True; +end; procedure TfrmTableEditor.btnHelpClick(Sender: TObject); begin @@ -2849,7 +3269,7 @@ procedure TfrmTableEditor.menuPasteColumnsClick(Sender: TObject); finally listcolumns.EndUpdate; end; - listColumns.Invalidate; + listColumns.Repaint; Modification(Sender); ColsFromClp.Free; end; diff --git a/source/tabletools.dfm b/source/tabletools.dfm index c338d78ba..fbace92eb 100644 --- a/source/tabletools.dfm +++ b/source/tabletools.dfm @@ -22,8 +22,8 @@ object frmTableTools: TfrmTableTools object lblCheckedSize: TLabel Left = 8 Top = 355 - Width = 70 - Height = 13 + Width = 79 + Height = 14 Anchors = [akLeft, akBottom] Caption = 'lblCheckedSize' end @@ -54,68 +54,30 @@ object frmTableTools: TfrmTableTools BevelOuter = bvNone TabOrder = 0 object spltHorizontally: TSplitter - Left = 144 + Left = 185 Top = 0 Width = 4 Height = 336 Cursor = crSizeWE ResizeStyle = rsUpdate - end - object TreeObjects: TVirtualStringTree - Left = 0 - Top = 0 - Width = 144 - Height = 336 - Align = alLeft - Header.AutoSizeIndex = 0 - Header.Options = [hoAutoResize, hoColumnResize, hoDrag, hoShowSortGlyphs] - Images = MainForm.VirtualImageListMain - IncrementalSearch = isInitializedOnly - PopupMenu = popupTree - TabOrder = 0 - TreeOptions.MiscOptions = [toAcceptOLEDrop, toCheckSupport, toFullRepaintOnResize, toInitOnSave, toToggleOnDblClick, toWheelPanning, toEditOnClick] - TreeOptions.PaintOptions = [toHotTrack, toShowButtons, toShowDropmark, toShowRoot, toShowTreeLines, toThemeAware, toUseBlendedImages, toGhostedIfUnfocused, toUseExplorerTheme, toHideTreeLinesIfThemed] - TreeOptions.SelectionOptions = [toFullRowSelect, toRightClickSelect] - OnBeforeCellPaint = TreeObjectsBeforeCellPaint - OnChange = TreeObjectsChange - OnChecked = TreeObjectsChecked - OnChecking = TreeObjectsChecking - OnExpanded = TreeObjectsExpanded - OnGetText = TreeObjectsGetText - OnPaintText = TreeObjectsPaintText - OnGetImageIndex = TreeObjectsGetImageIndex - OnGetNodeDataSize = TreeObjectsGetNodeDataSize - OnInitChildren = TreeObjectsInitChildren - OnInitNode = TreeObjectsInitNode - Touch.InteractiveGestures = [igPan, igPressAndTap] - Touch.InteractiveGestureOptions = [igoPanSingleFingerHorizontal, igoPanSingleFingerVertical, igoPanInertia, igoPanGutter, igoParentPassthrough] - Columns = < - item - Position = 0 - Text = 'Dummy, keeps compatibility to mainform.dbtree' - Width = 90 - end - item - Alignment = taRightJustify - Position = 1 - Text = 'Size' - end> + OnMoved = spltHorizontallyMoved end object pnlRight: TPanel - Left = 148 + Left = 189 Top = 0 - Width = 600 + Width = 559 Height = 336 Align = alClient BevelOuter = bvNone - TabOrder = 1 + TabOrder = 0 object ResultGrid: TVirtualStringTree Left = 0 Top = 193 - Width = 600 + Width = 559 Height = 143 Align = alClient Header.AutoSizeIndex = -1 + Header.Height = 14 Header.Images = MainForm.VirtualImageListMain Header.MainColumn = -1 Header.Options = [hoColumnResize, hoDblClickResize, hoDrag, hoHotTrack, hoShowSortGlyphs, hoVisible, hoDisableAnimatedResize, hoAutoResizeInclCaption] @@ -138,7 +100,7 @@ object frmTableTools: TfrmTableTools object tabsTools: TPageControl Left = 0 Top = 0 - Width = 600 + Width = 559 Height = 193 ActivePage = tabSQLexport Align = alTop @@ -150,27 +112,27 @@ object frmTableTools: TfrmTableTools ImageIndex = 39 ImageName = 'icons8-support' DesignSize = ( - 592 + 551 164) object lblOperation: TLabel Left = 3 Top = 14 - Width = 52 - Height = 13 + Width = 58 + Height = 14 Caption = 'Operation:' end object lblOptions: TLabel Left = 3 Top = 39 - Width = 41 - Height = 13 + Width = 46 + Height = 14 Caption = 'Options:' end object comboOperation: TComboBox Left = 80 Top = 11 - Width = 508 - Height = 21 + Width = 467 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] TabOrder = 0 @@ -231,7 +193,7 @@ object frmTableTools: TfrmTableTools OnClick = ValidateControls end object btnHelpMaintenance: TButton - Left = 514 + Left = 473 Top = 38 Width = 75 Height = 25 @@ -255,36 +217,36 @@ object frmTableTools: TfrmTableTools ImageIndex = 30 ImageName = 'icons8-find' DesignSize = ( - 592 + 551 164) object lblFindText: TLabel Left = 3 Top = 27 - Width = 60 - Height = 13 + Width = 70 + Height = 14 Caption = 'Text to find:' end object lblDataTypes: TLabel Left = 3 Top = 90 - Width = 114 - Height = 13 + Width = 131 + Height = 14 Anchors = [akLeft, akBottom] Caption = 'Search in column types:' end object lblMatchType: TLabel Left = 3 Top = 140 - Width = 58 - Height = 13 + Width = 66 + Height = 14 Anchors = [akLeft, akBottom] Caption = 'Match type:' end object comboDataTypes: TComboBox Left = 208 Top = 87 - Width = 381 - Height = 21 + Width = 340 + Height = 22 Style = csDropDownList Anchors = [akLeft, akRight, akBottom] TabOrder = 0 @@ -292,7 +254,7 @@ object frmTableTools: TfrmTableTools object chkCaseSensitive: TCheckBox Left = 208 Top = 114 - Width = 381 + Width = 340 Height = 17 Anchors = [akLeft, akRight, akBottom] Caption = 'Case sensitive' @@ -301,8 +263,8 @@ object frmTableTools: TfrmTableTools object comboMatchType: TComboBox Left = 208 Top = 137 - Width = 381 - Height = 21 + Width = 340 + Height = 22 Style = csDropDownList Anchors = [akLeft, akRight, akBottom] ItemIndex = 0 @@ -318,7 +280,7 @@ object frmTableTools: TfrmTableTools object tabsTextType: TPageControl Left = 208 Top = 3 - Width = 381 + Width = 340 Height = 78 ActivePage = tabSimpleText Anchors = [akLeft, akTop, akRight, akBottom] @@ -329,8 +291,8 @@ object frmTableTools: TfrmTableTools object memoFindText: TMemo Left = 0 Top = 0 - Width = 373 - Height = 50 + Width = 332 + Height = 49 Align = alClient ScrollBars = ssVertical TabOrder = 0 @@ -343,8 +305,8 @@ object frmTableTools: TfrmTableTools object SynMemoFindText: TSynMemo Left = 0 Top = 0 - Width = 373 - Height = 50 + Width = 332 + Height = 49 SingleLineMode = False Align = alClient Font.Charset = DEFAULT_CHARSET @@ -382,59 +344,59 @@ object frmTableTools: TfrmTableTools ImageIndex = 9 ImageName = 'icons8-outgoing-data-100' DesignSize = ( - 592 + 551 164) object lblExportData: TLabel Left = 3 Top = 50 - Width = 27 - Height = 13 + Width = 29 + Height = 14 Caption = 'Data:' end object lblExportOutputType: TLabel Left = 3 Top = 104 - Width = 38 - Height = 13 + Width = 44 + Height = 14 Caption = 'Output:' end object lblExportDatabases: TLabel Left = 3 Top = 4 - Width = 63 - Height = 13 + Width = 69 + Height = 14 Caption = 'Database(s):' end object lblExportTables: TLabel Left = 3 Top = 25 - Width = 43 - Height = 13 + Width = 49 + Height = 14 Caption = 'Table(s):' end object lblExportOutputTarget: TLabel Left = 2 Top = 130 - Width = 46 - Height = 13 + Width = 51 + Height = 14 Caption = 'Filename:' end object lblInsertSize: TLabel Left = 3 Top = 77 - Width = 84 - Height = 13 + Width = 93 + Height = 14 Caption = 'Max INSERT size:' end object lblInsertSizeUnit: TLabel Left = 242 Top = 77 - Width = 115 - Height = 13 + Width = 134 + Height = 14 Caption = 'KB (0 = Single INSERTs)' end object btnExportOutputTargetSelect: TButton - Left = 566 + Left = 525 Top = 127 Width = 23 Height = 21 @@ -485,8 +447,8 @@ object frmTableTools: TfrmTableTools object comboExportData: TComboBox Left = 100 Top = 47 - Width = 489 - Height = 21 + Width = 448 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] TabOrder = 4 @@ -495,19 +457,21 @@ object frmTableTools: TfrmTableTools object comboExportOutputType: TComboBox Left = 100 Top = 101 - Width = 489 - Height = 21 - Style = csDropDownList + Width = 448 + Height = 22 + Style = csOwnerDrawVariable Anchors = [akLeft, akTop, akRight] DropDownCount = 16 TabOrder = 7 OnChange = comboExportOutputTypeChange + OnDrawItem = comboExportOutputTypeDrawItem + OnMeasureItem = comboExportOutputTypeMeasureItem end object comboExportOutputTarget: TComboBox Left = 100 Top = 127 - Width = 463 - Height = 21 + Width = 422 + Height = 22 Anchors = [akLeft, akTop, akRight] DropDownCount = 16 ParentShowHint = False @@ -520,7 +484,7 @@ object frmTableTools: TfrmTableTools Left = 100 Top = 74 Width = 120 - Height = 21 + Height = 22 TabOrder = 5 Text = '0' end @@ -528,14 +492,14 @@ object frmTableTools: TfrmTableTools Left = 220 Top = 74 Width = 16 - Height = 21 + Height = 22 Associate = editInsertSize Max = 2147483647 TabOrder = 6 Wrap = True end object btnExportOptions: TButton - Left = 464 + Left = 423 Top = 72 Width = 125 Height = 25 @@ -552,7 +516,7 @@ object frmTableTools: TfrmTableTools ImageIndex = 19 ImageName = 'icons8-sheets-100' DesignSize = ( - 592 + 551 164) object chkBulkTableEditDatabase: TCheckBox Left = 3 @@ -566,8 +530,8 @@ object frmTableTools: TfrmTableTools object comboBulkTableEditDatabase: TComboBox Left = 208 Top = 3 - Width = 380 - Height = 21 + Width = 339 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] Enabled = False @@ -594,8 +558,8 @@ object frmTableTools: TfrmTableTools object comboBulkTableEditCollation: TComboBox Left = 208 Top = 49 - Width = 380 - Height = 21 + Width = 339 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] DropDownCount = 16 @@ -615,8 +579,8 @@ object frmTableTools: TfrmTableTools object comboBulkTableEditEngine: TComboBox Left = 208 Top = 26 - Width = 380 - Height = 21 + Width = 339 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] Enabled = False @@ -634,8 +598,8 @@ object frmTableTools: TfrmTableTools object comboBulkTableEditCharset: TComboBox Left = 208 Top = 72 - Width = 380 - Height = 21 + Width = 339 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] DropDownCount = 16 @@ -643,6 +607,159 @@ object frmTableTools: TfrmTableTools TabOrder = 8 end end + object tabGenerateData: TTabSheet + Caption = 'Generate data' + ImageIndex = 130 + object lblGenerateDataNumRows: TLabel + Left = 3 + Top = 6 + Width = 92 + Height = 14 + Caption = 'Number of rows:' + end + object lblGenerateDataNullAmount: TLabel + Left = 2 + Top = 34 + Width = 126 + Height = 14 + Caption = 'Amount of NULLs [percent]:' + end + object editGenerateDataNumRows: TEdit + Left = 200 + Top = 3 + Width = 121 + Height = 22 + TabOrder = 0 + Text = '1.000' + end + object updownGenerateDataNumRows: TUpDown + Left = 321 + Top = 3 + Width = 20 + Height = 22 + Associate = editGenerateDataNumRows + Min = 1 + Max = 2147483647 + Position = 1000 + TabOrder = 1 + end + object editGenerateDataNullAmount: TEdit + Left = 200 + Top = 31 + Width = 121 + Height = 22 + TabOrder = 2 + Text = '10' + end + object updownGenerateDataNullAmount: TUpDown + Left = 321 + Top = 31 + Width = 20 + Height = 22 + Associate = editGenerateDataNullAmount + Position = 10 + TabOrder = 3 + end + end + end + end + object pnlLeft: TPanel + Left = 0 + Top = 0 + Width = 185 + Height = 336 + Align = alLeft + BevelOuter = bvNone + Caption = 'pnlLeft' + ShowCaption = False + TabOrder = 1 + object pnlLeftTop: TPanel + Left = 0 + Top = 0 + Width = 185 + Height = 29 + Align = alTop + BevelOuter = bvNone + Caption = 'pnlLeftTop' + ShowCaption = False + TabOrder = 0 + object editDatabaseFilter: TButtonedEdit + Left = 6 + Top = 1 + Width = 49 + Height = 22 + Hint = + 'Database filter|A list of databases, separated by semicolon. Can' + + ' contain regular expressions, e.g. "mydb;test.*;project\d+".' + Images = MainForm.VirtualImageListMain + LeftButton.ImageIndex = 53 + LeftButton.Visible = True + RightButton.ImageIndex = 193 + TabOrder = 0 + Text = 'editDatabaseFilter' + TextHint = 'Database filter' + OnChange = editDatabaseTableFilterChange + OnKeyPress = editDatabaseTableFilterKeyPress + OnRightButtonClick = editDatabaseTableFilterRightButtonClick + end + object editTableFilter: TButtonedEdit + Left = 61 + Top = 1 + Width = 68 + Height = 22 + Hint = 'Table filter|Can contain regular expressions, e.g. "phpbb_\d"' + Images = MainForm.VirtualImageListMain + LeftButton.ImageIndex = 53 + LeftButton.Visible = True + RightButton.ImageIndex = 193 + TabOrder = 1 + Text = 'editTableFilter' + TextHint = 'Table filter' + OnChange = editDatabaseTableFilterChange + OnKeyPress = editDatabaseTableFilterKeyPress + OnRightButtonClick = editDatabaseTableFilterRightButtonClick + end + end + object TreeObjects: TVirtualStringTree + Left = 0 + Top = 29 + Width = 185 + Height = 307 + Align = alClient + Header.AutoSizeIndex = 0 + Header.Height = 18 + Header.Options = [hoAutoResize, hoColumnResize, hoDrag, hoShowSortGlyphs] + Images = MainForm.VirtualImageListMain + IncrementalSearch = isInitializedOnly + PopupMenu = popupTree + TabOrder = 1 + TreeOptions.MiscOptions = [toAcceptOLEDrop, toCheckSupport, toFullRepaintOnResize, toInitOnSave, toToggleOnDblClick, toWheelPanning, toEditOnClick] + TreeOptions.PaintOptions = [toHotTrack, toShowButtons, toShowDropmark, toShowRoot, toShowTreeLines, toThemeAware, toUseBlendedImages, toGhostedIfUnfocused, toUseExplorerTheme, toHideTreeLinesIfThemed] + TreeOptions.SelectionOptions = [toFullRowSelect, toRightClickSelect] + OnBeforeCellPaint = TreeObjectsBeforeCellPaint + OnChange = TreeObjectsChange + OnChecked = TreeObjectsChecked + OnChecking = TreeObjectsChecking + OnExpanded = TreeObjectsExpanded + OnGetText = TreeObjectsGetText + OnPaintText = TreeObjectsPaintText + OnGetImageIndex = TreeObjectsGetImageIndex + OnGetNodeDataSize = TreeObjectsGetNodeDataSize + OnInitChildren = TreeObjectsInitChildren + OnInitNode = TreeObjectsInitNode + Touch.InteractiveGestures = [igPan, igPressAndTap] + Touch.InteractiveGestureOptions = [igoPanSingleFingerHorizontal, igoPanSingleFingerVertical, igoPanInertia, igoPanGutter, igoParentPassthrough] + Columns = < + item + Position = 0 + Text = 'Dummy, keeps compatibility to mainform.dbtree' + Width = 131 + end + item + Alignment = taRightJustify + Position = 1 + Text = 'Size' + end> end end end @@ -674,10 +791,17 @@ object frmTableTools: TfrmTableTools Top = 352 object menuCheckNone: TMenuItem Caption = 'Check none' + ImageIndex = 65 OnClick = CheckAllClick end object menuCheckAll: TMenuItem Caption = 'Check all' + ImageIndex = 64 + OnClick = CheckAllClick + end + object menuInvertCheck: TMenuItem + Caption = 'Invert Check' + ImageIndex = 138 OnClick = CheckAllClick end object menuCheckByType: TMenuItem @@ -690,18 +814,33 @@ object frmTableTools: TfrmTableTools object menuExportAddComments: TMenuItem AutoCheck = True Caption = 'Add comments' + OnClick = menuExportOptionClick + end + object menuExportTransactions: TMenuItem + AutoCheck = True + Caption = 'Wrap data DML in transactions' + OnClick = menuExportOptionClick end object menuExportRemoveAutoIncrement: TMenuItem AutoCheck = True Caption = 'Remove AUTO_INCREMENT clauses' + OnClick = menuExportOptionClick end object menuExportRemoveDefiner: TMenuItem AutoCheck = True Caption = 'Remove DEFINER clauses' + OnClick = menuExportOptionClick end object menuCopyMysqldumpCommand: TMenuItem Caption = 'Copy mysqldump command' OnClick = menuCopyMysqldumpCommandClick end end + object timerCalcSize: TTimer + Enabled = False + Interval = 200 + OnTimer = timerCalcSizeTimer + Left = 264 + Top = 352 + end end diff --git a/source/tabletools.pas b/source/tabletools.pas index e4290a71c..9ef13c997 100644 --- a/source/tabletools.pas +++ b/source/tabletools.pas @@ -12,14 +12,14 @@ interface Winapi.Windows, System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls, Vcl.ComCtrls, Vcl.Buttons, Vcl.Dialogs, Vcl.StdActns, VirtualTrees, Vcl.ExtCtrls, Vcl.Graphics, SynRegExpr, System.Math, System.Generics.Collections, extra_controls, dbconnection, apphelpers, Vcl.Menus, gnugettext, System.DateUtils, System.Zip, System.UITypes, System.StrUtils, Winapi.Messages, - SynEdit, SynMemo, Vcl.ClipBrd, generic_types; + SynEdit, SynMemo, Vcl.ClipBrd, generic_types, VirtualTrees.Types, VirtualTrees.BaseAncestorVCL, + VirtualTrees.BaseTree, VirtualTrees.AncestorVCL, System.JSON, System.Variants; type - TToolMode = (tmMaintenance, tmFind, tmSQLExport, tmBulkTableEdit); + TToolMode = (tmMaintenance, tmFind, tmSQLExport, tmBulkTableEdit, tmGenerateData); TfrmTableTools = class(TExtForm) btnCloseOrCancel: TButton; pnlTop: TPanel; - TreeObjects: TVirtualStringTree; spltHorizontally: TSplitter; pnlRight: TPanel; ResultGrid: TVirtualStringTree; @@ -89,6 +89,21 @@ TfrmTableTools = class(TExtForm) memoFindText: TMemo; SynMemoFindText: TSynMemo; menuCopyMysqldumpCommand: TMenuItem; + pnlLeft: TPanel; + pnlLeftTop: TPanel; + editDatabaseFilter: TButtonedEdit; + editTableFilter: TButtonedEdit; + TreeObjects: TVirtualStringTree; + timerCalcSize: TTimer; + tabGenerateData: TTabSheet; + lblGenerateDataNumRows: TLabel; + editGenerateDataNumRows: TEdit; + updownGenerateDataNumRows: TUpDown; + lblGenerateDataNullAmount: TLabel; + editGenerateDataNullAmount: TEdit; + updownGenerateDataNullAmount: TUpDown; + menuInvertCheck: TMenuItem; + menuExportTransactions: TMenuItem; procedure FormCreate(Sender: TObject); procedure FormShow(Sender: TObject); procedure btnHelpMaintenanceClick(Sender: TObject); @@ -114,7 +129,7 @@ TfrmTableTools = class(TExtForm) procedure ResultGridPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); procedure ValidateControls(Sender: TObject); - procedure SaveSettings(Sender: TObject); + procedure SaveSettings; procedure chkExportOptionClick(Sender: TObject); procedure btnExportOutputTargetSelectClick(Sender: TObject); procedure comboExportOutputTargetChange(Sender: TObject); @@ -136,6 +151,16 @@ TfrmTableTools = class(TExtForm) procedure TreeObjectsExpanded(Sender: TBaseVirtualTree; Node: PVirtualNode); procedure btnExportOptionsClick(Sender: TObject); procedure menuCopyMysqldumpCommandClick(Sender: TObject); + procedure spltHorizontallyMoved(Sender: TObject); + procedure editDatabaseTableFilterChange(Sender: TObject); + procedure editDatabaseTableFilterKeyPress(Sender: TObject; var Key: Char); + procedure editDatabaseTableFilterRightButtonClick(Sender: TObject); + procedure timerCalcSizeTimer(Sender: TObject); + procedure comboExportOutputTypeDrawItem(Control: TWinControl; + Index: Integer; Rect: TRect; State: TOwnerDrawState); + procedure comboExportOutputTypeMeasureItem(Control: TWinControl; + Index: Integer; var Height: Integer); + procedure menuExportOptionClick(Sender: TObject); const StatusMsg = '%s %s ...'; private @@ -154,7 +179,7 @@ TfrmTableTools = class(TExtForm) FHeaderCreated: Boolean; FFindSeeResultSQL: TStringList; ToFile, ToDir, ToClipboard, ToDb, ToServer: Boolean; - FObjectSizes, FObjectSizesDone, FObjectSizesDoneExact: Int64; + FObjectCount, FObjectSizes, FObjectSizesDone, FObjectSizesDoneExact: Int64; FStartTimeAll: Cardinal; procedure WMNCLBUTTONDOWN(var Msg: TWMNCLButtonDown) ; message WM_NCLBUTTONDOWN; procedure WMNCLBUTTONUP(var Msg: TWMNCLButtonUp) ; message WM_NCLBUTTONUP; @@ -169,6 +194,9 @@ TfrmTableTools = class(TExtForm) procedure DoFind(DBObj: TDBObject); procedure DoExport(DBObj: TDBObject); procedure DoBulkTableEdit(DBObj: TDBObject); + procedure DoBeforeGenerateData(Sender: TObject); + procedure DoGenerateData(DBObj: TDBObject); + procedure DoAfterGenerateData(Sender: TObject); public { Public declarations } PreSelectObjects: TDBObjectList; @@ -235,6 +263,7 @@ procedure TfrmTableTools.FormCreate(Sender: TObject); MenuItem: TMenuItem; dt: TListNodeType; Obj: TDBObject; + Params: TConnectionParameters; begin HasSizeGrip := True; OUTPUT_FILE := _('Single .sql file'); @@ -268,6 +297,7 @@ procedure TfrmTableTools.FormCreate(Sender: TObject); comboExportData.ItemIndex := AppSettings.ReadInt(asExportSQLDataHow); updownInsertSize.Position := AppSettings.ReadInt(asExportSQLDataInsertSize); menuExportAddComments.Checked := AppSettings.ReadBool(asExportSQLAddComments); + menuExportTransactions.Checked := AppSettings.ReadBool(asExportSQLTransactions); menuExportRemoveAutoIncrement.Checked := AppSettings.ReadBool(asExportSQLRemoveAutoIncrement); menuExportRemoveDefiner.Checked := AppSettings.ReadBool(asExportSQLRemoveDefiner); // Add hardcoded output options and session names from registry @@ -280,12 +310,18 @@ procedure TfrmTableTools.FormCreate(Sender: TObject); SessionPaths := TStringList.Create; AppSettings.GetSessionPaths('', SessionPaths); for i:=0 to SessionPaths.Count-1 do begin - if SessionPaths[i] <> Mainform.ActiveConnection.Parameters.SessionPath then - comboExportOutputType.Items.Add(OUTPUT_SERVER+SessionPaths[i]); + if SessionPaths[i] = Mainform.ActiveConnection.Parameters.SessionPath then + Continue; + Params := TConnectionParameters.Create(SessionPaths[i]); + comboExportOutputType.Items.AddObject(OUTPUT_SERVER+SessionPaths[i], Params); end; SessionPaths.Free; comboExportOutputTarget.Text := ''; + // Generate data tab + updownGenerateDataNumRows.Position := AppSettings.ReadInt(asGenerateDataNumRows); + updownGenerateDataNullAmount.Position := AppSettings.ReadInt(asGenerateDataNullAmount); + // Various FixVT(TreeObjects); FixVT(ResultGrid); @@ -309,6 +345,48 @@ procedure TfrmTableTools.FormCreate(Sender: TObject); end; +procedure TfrmTableTools.comboExportOutputTypeDrawItem(Control: TWinControl; + Index: Integer; Rect: TRect; State: TOwnerDrawState); +var + Params: TConnectionParameters; + Canv: TCanvas; + ItemImageIndex: Integer; +begin + Canv := comboExportOutputType.Canvas; + if odSelected in State then begin + Canv.Brush.Color := clHighlight; + Canv.Pen.Color := clHighlightText; + end + else begin + Canv.Brush.Color := clWindow; + Canv.Pen.Color := clWindowText; + end; + + Params := comboExportOutputType.Items.Objects[Index] as TConnectionParameters; + if Assigned(Params) then begin + if (Params.SessionColor <> clNone) and (not (odSelected in State)) then begin + Canv.Brush.Color := Params.SessionColor; + Canv.Pen.Color := clWindowText; + end; + ItemImageIndex := Params.ImageIndex; + end + else begin + ItemImageIndex := MainForm.actExportTables.ImageIndex; + end; + + Canv.FillRect(Rect); + Canv.TextRect(Rect, Rect.Left + MainForm.VirtualImageListMain.Width + 4, Rect.Top, comboExportOutputType.Items[Index]); + MainForm.VirtualImageListMain.Draw(Canv, Rect.Left + 2, Rect.Top + 2, ItemImageIndex); +end; + + +procedure TfrmTableTools.comboExportOutputTypeMeasureItem(Control: TWinControl; + Index: Integer; var Height: Integer); +begin + Height := MainForm.VirtualImageListMain.Height + 2; +end; + + procedure TfrmTableTools.FormShow(Sender: TObject); var Node, FirstChecked: PVirtualNode; @@ -318,7 +396,7 @@ procedure TfrmTableTools.FormShow(Sender: TObject); // Restore GUI setup Width := AppSettings.ReadIntDpiAware(asTableToolsWindowWidth, Self); Height := AppSettings.ReadIntDpiAware(asTableToolsWindowHeight, Self); - TreeObjects.Width := AppSettings.ReadIntDpiAware(asTableToolsTreeWidth, Self); + pnlLeft.Width := AppSettings.ReadIntDpiAware(asTableToolsTreeWidth, Self); // When this form is displayed the second time, databases may be deleted or filtered. // Also, checked nodes must be unchecked and unchecked nodes may need to be checked. @@ -326,6 +404,7 @@ procedure TfrmTableTools.FormShow(Sender: TObject); TreeObjects.RootNodeCount := Mainform.DBtree.RootNodeCount; FObjectSizes := 0; + FObjectCount := 0; // Init all objects in active database, so the tree does not just check the db node // if we want the first child only. See issue #2267. @@ -379,6 +458,14 @@ procedure TfrmTableTools.FormShow(Sender: TObject); MainForm.SetupSynEditors(Self); MainForm.SynCompletionProposal.AddEditor(SynMemoFindText); + + pnlLeftTop.Height := editDatabaseFilter.Height + 2; + // Fixes width of filter edits: + spltHorizontallyMoved(Self); + // Apply filters: + editDatabaseFilter.Text := MainForm.editDatabaseFilter.Text; + editTableFilter.Text := MainForm.editTableFilter.Text; + ValidateControls(Sender); end; @@ -456,19 +543,34 @@ procedure TfrmTableTools.menuCopyMysqldumpCommandClick(Sender: TObject); end; +procedure TfrmTableTools.menuExportOptionClick(Sender: TObject); +var + i: Integer; + MenuItem: TMenuItem; +begin + // Display number of checked options in button caption + i := 0; + for MenuItem in popupExportOptions.Items do begin + if MenuItem.Checked then + Inc(i); + end; + btnExportOptions.Caption := _('Options') + ' (' + i.ToString + ')'; +end; + procedure TfrmTableTools.FormClose(Sender: TObject; var Action: TCloseAction); begin // Auto close temorary connection if Assigned(FTargetConnection) then FreeAndNil(FTargetConnection); + SaveSettings; // Save GUI setup AppSettings.WriteIntDpiAware(asTableToolsWindowWidth, Self, Width); AppSettings.WriteIntDpiAware(asTableToolsWindowHeight, Self, Height); - AppSettings.WriteIntDpiAware(asTableToolsTreeWidth, Self, TreeObjects.Width); + AppSettings.WriteIntDpiAware(asTableToolsTreeWidth, Self, pnlLeft.Width); end; -procedure TfrmTableTools.SaveSettings(Sender: TObject); +procedure TfrmTableTools.SaveSettings; var i: Integer; Items: TStringList; @@ -490,6 +592,7 @@ procedure TfrmTableTools.SaveSettings(Sender: TObject); if comboExportData.ItemIndex > 0 then AppSettings.WriteInt(asExportSQLDataInsertSize, updownInsertSize.Position); AppSettings.WriteBool(asExportSQLAddComments, menuExportAddComments.Checked); + AppSettings.WriteBool(asExportSQLTransactions, menuExportTransactions.Checked); AppSettings.WriteBool(asExportSQLRemoveAutoIncrement, menuExportRemoveAutoIncrement.Checked); AppSettings.WriteBool(asExportSQLRemoveDefiner, menuExportRemoveDefiner.Checked); @@ -525,6 +628,11 @@ procedure TfrmTableTools.SaveSettings(Sender: TObject); end; end; + tmGenerateData: begin + AppSettings.WriteInt(asGenerateDataNumRows, updownGenerateDataNumRows.Position); + AppSettings.WriteInt(asGenerateDataNullAmount, updownGenerateDataNullAmount.Position); + end; + end; end; @@ -540,7 +648,8 @@ procedure TfrmTableTools.ValidateControls(Sender: TObject); SomeChecked := TreeObjects.CheckedCount > 0; TExtForm.PageControlTabHighlight(tabsTools); btnSeeResults.Visible := tabsTools.ActivePage = tabFind; - lblCheckedSize.Caption := f_('Selected objects size: %s', [FormatByteNumber(FObjectSizes)]); + lblCheckedSize.Caption := f_('%s selected objects, size: %s', [FObjectCount.ToString, FormatByteNumber(FObjectSizes)]); + menuExportOptionClick(Sender); if tabsTools.ActivePage = tabMaintenance then begin btnExecute.Caption := _('Execute'); btnExecute.Enabled := (Pos(_(SUnsupported), comboOperation.Text) = 0) and SomeChecked; @@ -587,7 +696,11 @@ procedure TfrmTableTools.ValidateControls(Sender: TObject); OptionChecked := chkBulkTableEditDatabase.Checked or chkBulkTableEditEngine.Checked or chkBulkTableEditCollation.Checked or chkBulkTableEditCharset.Checked or chkBulkTableEditResetAutoinc.Checked; btnExecute.Enabled := SomeChecked and OptionChecked; + end else if tabsTools.ActivePage = tabGenerateData then begin + btnExecute.Caption := _('Generate'); + btnExecute.Enabled := SomeChecked; end; + end; @@ -607,18 +720,13 @@ procedure TfrmTableTools.TreeObjectsChange(Sender: TBaseVirtualTree; Node: PVirt procedure TfrmTableTools.TreeObjectsChecked(Sender: TBaseVirtualTree; Node: PVirtualNode); var Obj: PDBObject; - ObjSize: Int64; begin // Track sum of checked objects size Obj := Sender.GetNodeData(Node); - ObjSize := Max(Obj.Size, 0); - if Node.CheckState in CheckedStates then - Inc(FObjectSizes, ObjSize) - else - Dec(FObjectSizes, ObjSize); if Obj.NodeType = lntDb then FillTargetDatabases; - ValidateControls(Sender); + timerCalcSize.Enabled := False; + timerCalcSize.Enabled := True; end; @@ -773,6 +881,7 @@ procedure TfrmTableTools.Execute(Sender: TObject); tmFind: DoFind(DBObj); tmSQLExport: DoExport(DBObj); tmBulkTableEdit: DoBulkTableEdit(DBObj); + tmGenerateData: DoGenerateData(DBObj); end; except on E:EDbError do begin @@ -813,7 +922,9 @@ procedure TfrmTableTools.Execute(Sender: TObject); else if tabsTools.ActivePage = tabSQLExport then FToolMode := tmSQLExport else if tabsTools.ActivePage = tabBulkTableEdit then - FToolMode := tmBulkTableEdit; + FToolMode := tmBulkTableEdit + else if tabsTools.ActivePage = tabGenerateData then + FToolMode := tmGenerateData; ResultGrid.Clear; ResultGrid.TrySetFocus; FResults.Clear; @@ -828,6 +939,8 @@ procedure TfrmTableTools.Execute(Sender: TObject); MainForm.EnableProgress(100); FStartTimeAll := GetTickCount; + DoBeforeGenerateData(Sender); + SessionNode := TreeObjects.GetFirstChild(nil); while Assigned(SessionNode) do begin DBNode := TreeObjects.GetFirstChild(SessionNode); @@ -875,6 +988,8 @@ procedure TfrmTableTools.Execute(Sender: TObject); Conn := Mainform.ActiveConnection; + DoAfterGenerateData(Sender); + if Assigned(ExportStream) then begin // For output to file or directory: Output(EXPORT_FILE_FOOTER, False, True, False, False, False); @@ -931,7 +1046,6 @@ procedure TfrmTableTools.Execute(Sender: TObject); tabsTools.Enabled := True; treeObjects.Enabled := True; ValidateControls(Sender); - SaveSettings(Sender); Screen.Cursor := crDefault; end; @@ -956,6 +1070,93 @@ procedure TfrmTableTools.DoMaintenance(DBObj: TDBObject); end; +procedure TfrmTableTools.editDatabaseTableFilterKeyPress(Sender: TObject; + var Key: Char); +begin + if Key = #27 then + (Sender as TButtonedEdit).OnRightButtonClick(Sender); +end; + +procedure TfrmTableTools.editDatabaseTableFilterRightButtonClick(Sender: TObject); +begin + // Click on "clear" button of any TButtonedEdit control + TButtonedEdit(Sender).Clear; +end; + +procedure TfrmTableTools.editDatabaseTableFilterChange(Sender: TObject); +var + Node: PVirtualNode; + Obj: PDBObject; + rxdb, rxtable: TRegExpr; + NodeMatches, SomeHidden: Boolean; + Errors: TStringList; +begin + // Immediately apply database filter + MainForm.LogSQL('editDatabaseTableFilterChange', lcDebug); + + rxdb := TRegExpr.Create; + rxdb.ModifierI := True; + rxdb.Expression := '('+StringReplace(editDatabaseFilter.Text, ';', '|', [rfReplaceAll])+')'; + rxtable := TRegExpr.Create; + rxtable.ModifierI := True; + rxtable.Expression := '('+StringReplace(editTableFilter.Text, ';', '|', [rfReplaceAll])+')'; + + Errors := TStringList.Create; + SomeHidden := False; + + TreeObjects.BeginUpdate; + Node := TreeObjects.GetFirst; + while Assigned(Node) do begin + Obj := TreeObjects.GetNodeData(Node); + NodeMatches := True; + try + case Obj.NodeType of + lntDb: begin + // Match against database filter + if editDatabaseFilter.Text <> '' then + NodeMatches := rxdb.Exec(TreeObjects.Text[Node, 0]); + end; + lntTable..lntEvent: begin + // Match against table filter + if editTableFilter.Text <> '' then + NodeMatches := rxtable.Exec(TreeObjects.Text[Node, 0]); + // no favorites supported on table tools dialog + //if actFavoriteObjectsOnly.Checked then + // Hide non-favorite object path + //NodeMatches := NodeMatches and (Obj.Connection.Favorites.IndexOf(Obj.Path) > -1); + end; + end; + except + on E:Exception do begin + // Log regex errors, but avoid duplicate messages + if Errors.IndexOf(E.Message) = -1 then begin + MainForm.LogSQL(E.Message); + Errors.Add(E.Message); + end; + end; + end; + TreeObjects.IsVisible[Node] := NodeMatches; + if not NodeMatches then + SomeHidden := True; + + Node := TreeObjects.GetNextInitialized(Node); + end; + TreeObjects.EndUpdate; + + rxdb.Free; + rxtable.Free; + + editDatabaseFilter.RightButton.Visible := editDatabaseFilter.Text <> ''; + editTableFilter.RightButton.Visible := editTableFilter.Text <> ''; + if SomeHidden then + menuCheckAll.Caption := _('Check all visible') + else + menuCheckAll.Caption := _('Check all'); + timerCalcSize.Enabled := False; + timerCalcSize.Enabled := True; +end; + + procedure TfrmTableTools.DoFind(DBObj: TDBObject); var Columns: TTableColumnList; @@ -1103,7 +1304,7 @@ procedure TfrmTableTools.DoFind(DBObj: TDBObject); SQL := 'SELECT '+ esc(DBObj.Database)+' AS '+DBObj.Connection.QuoteIdent('Database')+', '+ esc(DBObj.Name)+' AS '+DBObj.Connection.QuoteIdent('Table')+', '+ - DBObj.Connection.GetSQLSpecifity(spFuncCeil)+'(('+DBObj.Connection.GetSQLSpecifity(spFuncLength)+'('+RoutineDefinitionColumn+') - '+DBObj.Connection.GetSQLSpecifity(spFuncLength)+'(REPLACE('+RoutineDefinitionColumn+', '+esc(FindText)+', '+esc('')+'))) / '+DBObj.Connection.GetSQLSpecifity(spFuncLength)+'('+esc(FindText)+')) AS '+DBObj.Connection.QuoteIdent('Found rows')+', '+ + DBObj.Connection.SqlProvider.GetSql(qFuncCeil)+'(('+DBObj.Connection.SqlProvider.GetSql(qFuncLength)+'('+RoutineDefinitionColumn+') - '+DBObj.Connection.SqlProvider.GetSql(qFuncLength)+'(REPLACE('+RoutineDefinitionColumn+', '+esc(FindText)+', '+esc('')+'))) / '+DBObj.Connection.SqlProvider.GetSql(qFuncLength)+'('+esc(FindText)+')) AS '+DBObj.Connection.QuoteIdent('Found rows')+', '+ '0 AS '+DBObj.Connection.QuoteIdent('Relevance')+ 'FROM '+DBObj.Connection.QuoteIdent(DBObj.Connection.InfSch)+'.'+DBObj.Connection.QuoteIdent('routines')+' '+ 'WHERE '+DBObj.Connection.QuoteIdent(RoutineSchemaColumn)+'='+esc(DBObj.Database)+' AND '+DBObj.Connection.QuoteIdent('routine_name')+'='+esc(DBObj.Name); @@ -1146,6 +1347,7 @@ procedure TfrmTableTools.AddResults(SQL: String; Connection: TDBConnection); begin // Execute query and append results into grid Results := Connection.GetResults(SQL); + Connection.ShowWarnings; if Results = nil then Exit; @@ -1232,6 +1434,42 @@ procedure TfrmTableTools.SetupResultGrid(Results: TDBQuery=nil); end; +procedure TfrmTableTools.spltHorizontallyMoved(Sender: TObject); +begin + editDatabaseFilter.Left := 0; + editDatabaseFilter.Width := (pnlLeftTop.Width div 2) - 1; + editTableFilter.Width := editDatabaseFilter.Width; + editTableFilter.Left := editDatabaseFilter.Width + 1; +end; + +procedure TfrmTableTools.timerCalcSizeTimer(Sender: TObject); +var + SessionNode, DBNode: PVirtualNode; + CheckedObjects: TDBObjectList; + DBObj: TDBObject; +begin + // Calculate object sizes and display on label + timerCalcSize.Enabled := False; + SessionNode := TreeObjects.GetFirstChild(nil); + FObjectSizes := 0; + FObjectCount := 0; + while Assigned(SessionNode) do begin + DBNode := TreeObjects.GetFirstChild(SessionNode); + while Assigned(DBNode) do begin + if not (DBNode.CheckState in [csUncheckedNormal, csUncheckedPressed]) then begin + CheckedObjects := GetCheckedObjects(DBNode); + for DBObj in CheckedObjects do begin + Inc(FObjectSizes, DBObj.Size); + Inc(FObjectCount); + end; + end; + DBNode := TreeObjects.GetNextSibling(DBNode); + end; + SessionNode := TreeObjects.GetNextSibling(SessionNode); + end; + ValidateControls(Sender); +end; + procedure TfrmTableTools.UpdateResultGrid; var Percent: Double; @@ -1344,8 +1582,13 @@ procedure TfrmTableTools.comboExportOutputTypeChange(Sender: TObject); comboExportOutputTarget.Items.Text := AppSettings.ReadString(asExportSQLFilenames, '') else comboExportOutputTarget.Items.Text := AppSettings.ReadString(asExportZIPFilenames, ''); - if comboExportOutputTarget.Items.Count > 0 then + if comboExportOutputTarget.Items.Count > 0 then begin comboExportOutputTarget.ItemIndex := 0; + // Cut long file list down to 20 latest items + for i:=comboExportOutputTarget.Items.Count-1 downto 20 do begin + comboExportOutputTarget.Items.Delete(i); + end; + end; lblExportOutputTarget.Caption := _('Filename')+':'; btnExportOutputTargetSelect.Enabled := True; btnExportOutputTargetSelect.ImageIndex := 51; @@ -1475,7 +1718,8 @@ procedure TfrmTableTools.btnExportOutputTargetSelectClick(Sender: TObject); SaveDialog.Filter := _('SQL files')+' (*.sql)|*.sql|'+_('All files')+' (*.*)|*.*' else SaveDialog.Filter := _('ZIP files')+' (*.zip)|*.zip|'+_('All files')+' (*.*)|*.*'; - SaveDialog.Options := SaveDialog.Options + [ofOverwritePrompt]; + // Don't prompt here if file exists, but later when exporting starts. See issue #835 + // SaveDialog.Options := SaveDialog.Options + [ofOverwritePrompt]; if SaveDialog.Execute then comboExportOutputTarget.Text := SaveDialog.FileName; SaveDialog.Free; @@ -1500,6 +1744,7 @@ procedure TfrmTableTools.SetToolMode(Value: TToolMode); tmFind: tabsTools.ActivePage := tabFind; tmSQLExport: tabsTools.ActivePage := tabSQLExport; tmBulkTableEdit: tabsTools.ActivePage := tabBulkTableEdit; + tmGenerateData: tabsTools.ActivePage := tabGenerateData; end; end; @@ -1545,10 +1790,12 @@ procedure TfrmTableTools.DoExport(DBObj: TDBObject); Limit, Offset, ResultCount, MaxInsertSize: Int64; StartTime: Cardinal; StrucResult, Data: TDBQuery; - ColumnList: TTableColumnList; + ColumnList, KeyColumns: TTableColumnList; + KeyList: TTableKeyList; Column: TTableColumn; Quoter: TDBConnection; - TargetFileName, SetCharsetCode: String; + TargetFileName, SetCharsetCode, ColumnsForSelect: String; + OrderBy: String; const TempDelim = '//'; AssumedAvgRowLen = 10000; @@ -1604,12 +1851,20 @@ procedure TfrmTableTools.DoExport(DBObj: TDBObject); DbDir := IncludeTrailingPathDelimiter(GetOutputFilename(comboExportOutputTarget.Text, DBObj)) + DBObj.Database + '\'; if not DirectoryExists(DbDir) then ForceDirectories(DbDir); - ExportStream := TFileStream.Create(DbDir + DBObj.Name+'.sql', fmCreate or fmOpenWrite); + ExportStream := TFileStream.Create(DbDir + DBObj.ObjType.ToLower + '-' + DBObj.Name+'.sql', fmCreate or fmOpenWrite); FHeaderCreated := False; end; if not Assigned(ExportStream) then begin if ToFile then begin TargetFileName := GetOutputFilename(comboExportOutputTarget.Text, DBObj); + if FileExists(TargetFileName) then begin + case MessageDialog(f_('File already exists: %s'+sLineBreak+sLineBreak+'Overwrite it?', [TargetFileName]), mtConfirmation, [mbYes, mbCancel]) of + mrYes:; + mrCancel: + raise EFCreateError.CreateFmt(_('Export cancelled, file not overwritten: %s'), [TargetFileName]); + end; + end; + FExportFileName := TargetFileName; if comboExportOutputType.Text = OUTPUT_FILE_COMPRESSED then TargetFileName := ChangeFileExt(TargetFileName, '_temp.sql'); @@ -1683,13 +1938,13 @@ procedure TfrmTableTools.DoExport(DBObj: TDBObject); end else Struc := 'CREATE DATABASE IF NOT EXISTS '+Quoter.QuoteIdent(FinalDbName); Output(Struc, True, NeedsDBStructure, False, False, NeedsDBStructure); - Output(Quoter.GetSQLSpecifity(spUSEQuery, [Quoter.QuoteIdent(FinalDbName)]), True, NeedsDBStructure, False, False, NeedsDBStructure); + Output(Quoter.SqlProvider.GetSql(qUSEQuery, [Quoter.QuoteIdent(FinalDbName)]), True, NeedsDBStructure, False, False, NeedsDBStructure); Output(CRLF, False, NeedsDBStructure, False, False, NeedsDBStructure); end; end; if ToServer and (not chkExportDatabasesCreate.Checked) then begin // Export to server without "CREATE/USE dbname" and "Same dbs as on source server" - needs a "USE dbname" - Output(Quoter.GetSQLSpecifity(spUSEQuery, [Quoter.QuoteIdent(FinalDbName)]), True, False, False, False, NeedsDBStructure); + Output(Quoter.SqlProvider.GetSql(qUSEQuery, [Quoter.QuoteIdent(FinalDbName)]), True, False, False, False, NeedsDBStructure); end; // Table structure @@ -1730,10 +1985,12 @@ procedure TfrmTableTools.DoExport(DBObj: TDBObject); // Prevent DEFAULT value from coming in, to fix errors due to multiple CURRENT_TIMESTAMP values // See issue #2748 Column.DefaultType := cdtNothing; - Struc := Struc + CRLF + #9 + Column.SQLCode + ','; + if Column.DataType.Index = dbdtVarchar then + Column.LengthSet := '1'; + Struc := Struc + sLineBreak + CodeIndent + Column.SQLCode + ','; end; Delete(Struc, Length(Struc), 1); - Struc := Struc + CRLF + ') ENGINE=MyISAM'; + Struc := Struc + CRLF + ')'; ColumnList.Free; end else begin Struc := ''; @@ -1747,6 +2004,8 @@ procedure TfrmTableTools.DoExport(DBObj: TDBObject); Struc := DBObj.GetCreateCode(False, menuExportRemoveDefiner.Checked); if ToDb then Insert(Quoter.QuoteIdent(FinalDbName)+'.', Struc, Pos('VIEW', Struc) + 5 ); + // Issue #2050: Add new line at end to support comments at the end of a view definition + Struc := Struc + sLineBreak; end; end; @@ -1812,24 +2071,43 @@ procedure TfrmTableTools.DoExport(DBObj: TDBObject); tmp := '~'+tmp+' ('+_('approximately')+')'; if menuExportAddComments.Checked then Output('-- '+f_('Dumping data for table %s.%s: %s', [DBObj.Database, DBObj.Name, tmp])+CRLF, False, True, True, False, False); + if menuExportTransactions.Checked then + Output('BEGIN', True, True, True, True, True); TargetDbAndObject := Quoter.QuoteIdent(DBObj.Name); if ToDb then TargetDbAndObject := Quoter.QuoteIdent(FinalDbName) + '.' + TargetDbAndObject; Offset := 0; RowCount := 0; + // Examine columns + ColumnList := DBObj.TableColumns; + if not ColumnList.HasInvisibleColumns then + ColumnsForSelect := '*' + else + ColumnsForSelect := ColumnList.QuoteIdents; + // Sort by primary key if one exists, see issue #2168 + KeyList := DBObj.TableKeys; + KeyColumns := DBObj.Connection.GetKeyColumns(ColumnList, KeyList); + OrderBy := ''; + for i:=0 to KeyColumns.Count-1 do begin + if i>0 then + OrderBy := OrderBy + ', '; + OrderBy := OrderBy + DBObj.Connection.QuoteIdent(KeyColumns[i].Name); + end; + if not OrderBy.IsEmpty then + OrderBy := ' ORDER BY ' + OrderBy; // Calculate limit so we select ~100MB per loop // Take care of disabled "Get full table status" session setting, where AvgRowLen is 0 Limit := Round(100 * SIZE_MB / IfThen(DBObj.AvgRowLen>0, DBObj.AvgRowLen, AssumedAvgRowLen)); - if comboExportData.Text = DATA_REPLACE then + if comboExportData.Text = DATA_REPLACE then begin Output('DELETE FROM '+TargetDbAndObject, True, True, True, True, True); - if DBObj.Engine.ToLowerInvariant <> 'innodb' then begin - Output('/*!40000 ALTER TABLE '+TargetDbAndObject+' DISABLE KEYS */', True, True, True, True, True); + if menuExportRemoveAutoIncrement.Checked then + Output('/*!50000 ALTER TABLE '+TargetDbAndObject+' AUTO_INCREMENT = 1 */', True, True, True, True, True); end; while true do begin Data := DBObj.Connection.GetResults( DBObj.Connection.ApplyLimitClause( 'SELECT', - '* FROM '+DBObj.QuotedDbAndTableName, + '/* '+APPNAME+' '+MainForm.AppVersion+' */ ' + ColumnsForSelect + ' FROM '+DBObj.QuotedDbAndTableName + OrderBy, Limit, Offset) ); @@ -1850,7 +2128,7 @@ procedure TfrmTableTools.DoExport(DBObj: TDBObject); BaseInsert := BaseInsert + Quoter.QuoteIdent(Data.ColumnNames[i]) + ', '; end; Delete(BaseInsert, Length(BaseInsert)-1, 2); - BaseInsert := BaseInsert + ') VALUES'+CRLF+#9+'('; + BaseInsert := BaseInsert + ') VALUES' + sLineBreak + CodeIndent + '('; while true do begin Output(BaseInsert, False, True, True, True, True); RowCountInChunk := 0; @@ -1858,19 +2136,13 @@ procedure TfrmTableTools.DoExport(DBObj: TDBObject); while not Data.Eof do begin Row := ''; if RowCountInChunk > 0 then - Row := Row + ','+CRLF+#9+'('; + Row := Row + ',' + sLineBreak + CodeIndent + '('; for i:=0 to Data.ColumnCount-1 do begin if Data.ColIsVirtual(i) then Continue; if Data.IsNull(i) then Row := Row + 'NULL' else case Data.DataType(i).Category of - dtcInteger, dtcReal: begin - if Data.DataType(i).Index = dbdtBit then - Row := Row + 'b' + Quoter.EscapeString(Data.Col(i)) - else - Row := Row + Data.Col(i); - end; dtcBinary, dtcSpatial: begin BinContent := Data.HexValue(i); if Length(BinContent) > 0 then @@ -1878,7 +2150,7 @@ procedure TfrmTableTools.DoExport(DBObj: TDBObject); else Row := Row + Quoter.EscapeString(''); end; - else Row := Row + Quoter.EscapeString(Data.Col(i)); + else Row := Row + Quoter.EscapeString(Data.Col(i), Data.DataType(i)); end; Row := Row + ', '; end; @@ -1908,9 +2180,8 @@ procedure TfrmTableTools.DoExport(DBObj: TDBObject); break; end; - if DBObj.Engine.ToLowerInvariant <> 'innodb' then begin - Output('/*!40000 ALTER TABLE '+TargetDbAndObject+' ENABLE KEYS */', True, True, True, True, True); - end; + if menuExportTransactions.Checked then + Output('COMMIT', True, True, True, True, True); Output(CRLF, False, True, True, True, True); // Cosmetic fix for estimated InnoDB row count DBObj.Rows := RowCount; @@ -2016,17 +2287,251 @@ procedure TfrmTableTools.DoBulkTableEdit(DBObj: TDBObject); end; +procedure TfrmTableTools.DoBeforeGenerateData(Sender: TObject); +var + Conn: TDBConnection; +begin + // Disable foreign key checks + if ToolMode <> tmGenerateData then + Exit; + Conn := MainForm.ActiveConnection; + if Conn.SqlProvider.Has(qDisableForeignKeyChecks) then + Conn.Query(qDisableForeignKeyChecks); +end; + + +procedure TfrmTableTools.DoAfterGenerateData(Sender: TObject); +var + Conn: TDBConnection; +begin + // Disable foreign key checks + if ToolMode <> tmGenerateData then + Exit; + Conn := MainForm.ActiveConnection; + if Conn.SqlProvider.Has(qEnableForeignKeyChecks) then + Conn.Query(qEnableForeignKeyChecks); +end; + + +procedure TfrmTableTools.DoGenerateData(DBObj: TDBObject); +var + Columns: TTableColumnList; + Col: TTableColumn; + InsertSqlBase, InsertSql: String; + ColumnNamesSkipped, ColumnNamesQuoted, Values: TStringList; + i, j: Integer; + IntVal, MaxLen, MinLen: Integer; + FloatVal: Extended; + JsonText: TJSONString; + EnumValues: TStringList; + TextVal: String; + BinVal: String; + + function RS: String; + begin + // return a Random integer as String + Result := RandomRange(1, 100).ToString; + end; +begin + // Generate rows + if not (DBObj.NodeType in [lntTable, lntView]) then begin + AddNotes(DBObj, STRSKIPPED+'cannot insert rows in a '+LowerCase(DBObj.ObjType), ''); + Exit; + end; + AddNotes(DBObj, 'Inserting '+FormatNumber(updownGenerateDataNumRows.Position)+' rows into '+DBObj.Name, ''); + UpdateResultGrid; + + Columns := DBObj.TableColumns; + + InsertSqlBase := 'INSERT INTO ' + DBObj.QuotedDbAndTableName + ' '; + ColumnNamesQuoted := TStringList.Create; + ColumnNamesSkipped := TStringList.Create; + Values := TStringList.Create; + for Col in Columns do begin + if Col.DefaultType = cdtAutoInc then begin + ColumnNamesSkipped.Add(Col.Name); + Continue; + end; + if (Col.DefaultType = cdtExpression) and ExecRegExprI('^(NOW()|CURRENT_TIMESTAMP)', Col.DefaultText) then begin + ColumnNamesSkipped.Add(Col.Name); + Continue; + end; + + ColumnNamesQuoted.Add(Col.Connection.QuoteIdent(Col.Name)); + end; + InsertSqlBase := InsertSqlBase + '(' + Implode(', ', ColumnNamesQuoted) + ') VALUES '; + + Randomize; + + for i:=1 to updownGenerateDataNumRows.Position do begin + Values.Clear; + // Generate random values. Include some NULLs for columns which allow that. + for Col in Columns do begin + if ColumnNamesSkipped.Contains(Col.Name) then + Continue; + + // https://www.delphipraxis.net/31059-warscheinlichkeit-random.html + if Col.AllowNull + and (updownGenerateDataNullAmount.Position > 0) // prevent division by zero + and (Random < (updownGenerateDataNullAmount.Position / 100)) + then begin + Values.Add('NULL'); + Continue; + end; + + case Col.DataType.Category of + dtcInteger: begin + // Take care of overflow in RandomRange with signed integers + IntVal := 0; + case Col.DataType.Index of + dbdtTinyint: + IntVal := IfThen(Col.Unsigned, RandomRange(0, 256), RandomRange(-128, 128)); + dbdtSmallint: + IntVal := IfThen(Col.Unsigned, RandomRange(0, 65535), RandomRange(-32768, 32768)); + dbdtMediumint: + IntVal := IfThen(Col.Unsigned, RandomRange(0, 16777215), RandomRange(-8388608, 8388608)); + dbdtUint: + IntVal := RandomRange(0, MaxInt); + dbdtInt, dbdtBigint: + IntVal := IfThen(Col.Unsigned, RandomRange(0, MaxInt), RandomRange(0 - MaxInt, MaxInt)); + end; + Values.Add(IntVal.ToString); + end; + + dtcReal: begin + FloatVal := 0; + case Col.DataType.Index of + dbdtFloat, dbdtDouble, dbdtDecimal, dbdtNumeric, dbdtReal, dbdtDoublePrecision, dbdtMoney, dbdtSmallmoney: + FloatVal := IfThen(Col.Unsigned, RandomRange(0, 100000), RandomRange(-100000, 100000)) + Random; + end; + Values.Add(FloatToStr(FloatVal, MainForm.FormatSettings)); + end; + + dtcText: begin + MaxLen := 0; + case Col.DataType.Index of + dbdtChar, dbdtVarchar: + MaxLen := StrToIntDef(Col.LengthSet, 1); + dbdtTinytext: + MaxLen := Trunc(Power(2, 8)) -1; + dbdtText, dbdtMediumtext, dbdtLongtext, dbdtJson, dbdtJsonB: + MaxLen := Trunc(Power(2, 16)) -1; + end; + TextVal := ''; + MaxLen := Min(MaxLen, SIZE_KB); + MaxLen := RandomRange(1, MaxLen+1); + for j:=1 to MaxLen do begin + if Random < 0.01 then + TextVal := TextVal + #10 // New line + else if Random < 0.05 then + TextVal := TextVal + ' ' // Space + else if Random < 0.1 then + TextVal := TextVal + Chr(RandomRange(65, 90)) // Uppercase letters + else + TextVal := TextVal + Chr(RandomRange(97, 122)); // Lowercase letters + end; + TextVal := Trim(TextVal); + if Col.DataType.Index in [dbdtJson, dbdtJsonB] then begin + JsonText := TJSONString.Create(TextVal); + TextVal := JsonText.ToJSON; + JsonText.Free; + end; + Values.Add(Col.Connection.EscapeString(TextVal)); + end; + + dtcBinary: begin + MaxLen := 0; + case Col.DataType.Index of + dbdtBinary, dbdtVarbinary: + MaxLen := StrToIntDef(Col.LengthSet, 1); + dbdtTinyblob: + MaxLen := Trunc(Power(2, 8)) -1; + dbdtBlob, dbdtMediumblob, dbdtLongblob: + MaxLen := Trunc(Power(2, 16)) -1; + end; + BinVal := ''; + MinLen := Min(16, MaxLen); + MaxLen := RandomRange(MinLen, MaxLen+1); + for j:=1 to MaxLen do begin + BinVal := BinVal + Chr(RandomRange(1, 256)); + end; + Values.Add(Col.Connection.EscapeBin(BinVal)); + end; + + dtcTemporal: begin + TextVal := ''; + case Col.DataType.Index of + dbdtDate, dbdtTime, dbdtYear, dbdtDatetime, dbdtDatetime2, dbdtTimestamp, dbdtInterval: begin + MinLen := Trunc(VarToDateTime('1971-01-01')); + MaxLen := Trunc(VarToDateTime('2035-01-01')); + FloatVal := RandomRange(MinLen, MaxLen) + Random; + TextVal := FormatDateTime(Col.DataType.Format, FloatVal, MainForm.FormatSettings); + end; + + dbdtDatetimeOffset: ; + dbdtSmalldatetime: ; + end; + Values.Add(Col.Connection.EscapeString(TextVal)); + end; + + dtcSpatial: begin + TextVal := Col.Connection.EscapeString(''); + case Col.DataType.Index of + dbdtPoint: TextVal := 'POINT('+RS+', '+RS+')'; + dbdtMultipoint: TextVal := 'MULTIPOINT(POINT('+RS+', '+RS+'), POINT('+RS+', '+RS+'))'; + dbdtLinestring: TextVal := 'LINESTRING(POINT('+RS+', '+RS+'), POINT('+RS+', '+RS+'))'; + dbdtMultilinestring: TextVal := 'MULTILINESTRING(LINESTRING(POINT('+RS+', '+RS+'), POINT('+RS+', '+RS+')), LINESTRING(POINT('+RS+', '+RS+'), POINT('+RS+', '+RS+')))'; + dbdtPolygon: TextVal := 'POLYGON(LINESTRING(POINT('+RS+', '+RS+'), POINT('+RS+', '+RS+')), LINESTRING(POINT('+RS+', '+RS+'), POINT('+RS+', '+RS+')))'; + dbdtMultipolygon: TextVal := 'MULTIPOLYGON(POLYGON(LINESTRING(POINT('+RS+', '+RS+'), POINT('+RS+', '+RS+')), LINESTRING(POINT('+RS+', '+RS+'), POINT('+RS+', '+RS+'))))'; + dbdtGeometry: TextVal := 'POINT('+RS+', '+RS+')'; + dbdtGeometrycollection: TextVal := 'GEOMETRYCOLLECTION(POINT('+RS+', '+RS+'), LINESTRING(POINT('+RS+', '+RS+'), POINT('+RS+', '+RS+')))'; + end; + Values.Add(TextVal); + end; + + dtcOther: begin + case Col.DataType.Index of + dbdtEnum, dbdtSet: begin + EnumValues := Col.ValueList; + IntVal := RandomRange(0, EnumValues.Count); + TextVal := EnumValues[IntVal]; + EnumValues.Free; + Values.Add(Col.Connection.EscapeString(TextVal)); + end; + dbdtBool: begin + IntVal := RandomRange(0, 2); + TextVal := IfThen(IntVal=0, 'true', 'false'); + Values.Add(Col.Connection.EscapeString(TextVal)); + end; + else + Values.Add('0'); + end; + end; + end; + end; + InsertSql := InsertSqlBase + '(' + Implode(', ', Values) + ')'; + DBObj.Connection.Query(InsertSql, False, lcScript); + end; + + ColumnNamesQuoted.Free; + ColumnNamesSkipped.Free; + Values.Free; +end; + + procedure TfrmTableTools.CheckAllClick(Sender: TObject); var DBNode, ObjNode: PVirtualNode; WantedType: TListNodeType; DBObj: PDBObject; - CheckNone: Boolean; + CheckNone, DoCheck: Boolean; + InvertCheck: Boolean; CheckedNodes: Int64; begin // Check all/none/by type via context menu WantedType := TListNodeType((Sender as TMenuItem).Tag); CheckNone := Sender = menuCheckNone; + InvertCheck := Sender = menuInvertCheck; case TreeObjects.GetNodeLevel(TreeObjects.FocusedNode) of 1: DBNode := TreeObjects.FocusedNode; 2: DBNode := TreeObjects.FocusedNode.Parent; @@ -2037,19 +2542,29 @@ procedure TfrmTableTools.CheckAllClick(Sender: TObject); CheckedNodes := 0; while Assigned(ObjNode) do begin DBObj := TreeObjects.GetNodeData(ObjNode); + if CheckNone then - TreeObjects.CheckState[ObjNode] := csUncheckedNormal + DoCheck := False + else if not TreeObjects.IsVisible[ObjNode] then + DoCheck := False + else if InvertCheck then + DoCheck := not (ObjNode.CheckState in CheckedStates) + else + DoCheck := (WantedType = lntNone) or (DBObj.NodeType = WantedType) or (DBObj.GroupType = WantedType); + + if DoCheck then begin + TreeObjects.CheckState[ObjNode] := csCheckedNormal; + Inc(CheckedNodes); + end else begin - if (WantedType = lntNone) or (DBObj.NodeType = WantedType) or (DBObj.GroupType = WantedType) then - TreeObjects.CheckState[ObjNode] := csCheckedNormal - else - TreeObjects.CheckState[ObjNode] := csUncheckedNormal; + TreeObjects.CheckState[ObjNode] := csUncheckedNormal; end; - if ObjNode.CheckState = csCheckedNormal then - Inc(CheckedNodes); + TreeObjects.RepaintNode(ObjNode); ObjNode := TreeObjects.GetNextSibling(ObjNode); end; + + // Update parent node's checkbox if CheckedNodes = 0 then TreeObjects.CheckState[DBNode] := csUncheckedNormal else if CheckedNodes = TreeObjects.ChildCount[DBNode] then diff --git a/source/texteditor.pas b/source/texteditor.pas index 32f1b081d..6f06f4fa1 100644 --- a/source/texteditor.pas +++ b/source/texteditor.pas @@ -86,7 +86,8 @@ TfrmTextEditor = class(TExtForm) private { Private declarations } FModified: Boolean; - FStopping: Boolean; + FClosingByApplyButton: Boolean; + FClosingByCancelButton: Boolean; FDetectedLineBreaks, FSelectedLineBreaks: TLineBreaks; FMaxLength: Integer; @@ -141,11 +142,15 @@ procedure TfrmTextEditor.SetText(text: String); end; if Assigned(Detected) then SelectLineBreaks(Detected); - if (Length(text) > SIZE_MB) then begin - MainForm.LogSQL(_('Auto-disabling wordwrap for large text')); + if (Length(text) > SIZE_KB*10) then begin + MainForm.LogSQL(_('Auto-disabling wordwrap and syntax highlighter for large text')); btnWrap.Enabled := False; + comboHighlighter.Enabled := False; + btnCustomizeHighlighter.Enabled := False; end else begin btnWrap.Enabled := True; + comboHighlighter.Enabled := True; + btnCustomizeHighlighter.Enabled := True; end; MemoText.Text := text; @@ -242,6 +247,7 @@ procedure TfrmTextEditor.FormCreate(Sender: TObject); i: Integer; begin HasSizeGrip := True; + FClosingByApplyButton := False; // Assign linebreak values to their menu item tags, to write less code later menuWindowsLB.Tag := Integer(lbsWindows); menuUnixLB.Tag := Integer(lbsUnix); @@ -282,10 +288,16 @@ procedure TfrmTextEditor.FormDestroy(Sender: TObject); if btnWrap.Enabled then begin AppSettings.WriteBool(asMemoEditorWrap, btnWrap.Down); end; - if Assigned(FTableColumn) and (comboHighlighter.Text <> AppSettings.GetDefaultString(asMemoEditorHighlighter)) then begin + if Assigned(FTableColumn) then begin AppSettings.SessionPath := MainForm.GetRegKeyTable; - AppSettings.WriteString(asMemoEditorHighlighter, comboHighlighter.Text, FTableColumn.Name); + if comboHighlighter.Text <> AppSettings.GetDefaultString(asMemoEditorHighlighter) then + AppSettings.WriteString(asMemoEditorHighlighter, comboHighlighter.Text, FTableColumn.Name) + else + AppSettings.DeleteValue(asMemoEditorHighlighter, FTableColumn.Name); end; + // Fixes EAccessViolation under 64-bit when using non-default themes + if Assigned(Panel1) then + Panel1.Parent := nil; end; @@ -372,6 +384,8 @@ procedure TfrmTextEditor.comboHighlighterSelect(Sender: TObject); SelStart, SelLength: Integer; begin // Code highlighter selected + if not comboHighlighter.Enabled then + Exit; SelStart := MemoText.SelStart; SelLength := MemoText.SelLength; MemoText.Highlighter := nil; @@ -418,7 +432,7 @@ procedure TfrmTextEditor.btnLoadTextClick(Sender: TObject); Screen.Cursor := crHourglass; MemoText.Text := ReadTextFile(d.FileName, MainForm.GetEncodingByName(d.Encodings[d.EncodingIndex])); if (FMaxLength > 0) and (Length(MemoText.Text) > FMaxLength) then - MemoText.Text := copy(MemoText.Text, 0, FMaxLength); + MemoText.Text := Copy(MemoText.Text, 1, FMaxLength); AppSettings.WriteInt(asFileDialogEncoding, d.EncodingIndex, Self.Name); finally Screen.Cursor := crDefault; @@ -428,11 +442,9 @@ procedure TfrmTextEditor.btnLoadTextClick(Sender: TObject); procedure TfrmTextEditor.btnCancelClick(Sender: TObject); -var - Action: TCloseAction; begin - Action := caNone; - FormClose(Self, Action); + FClosingByCancelButton := True; + Close; end; @@ -499,27 +511,24 @@ procedure TfrmTextEditor.menuFormatCodeOnceClick(Sender: TObject); procedure TfrmTextEditor.FormClose(Sender: TObject; var Action: TCloseAction); -var - DoPost: Boolean; begin - if FStopping then - Exit; - FStopping := True; - if Modified then - DoPost := MessageDialog(_('Apply modifications?'), mtConfirmation, [mbYes, mbNo]) = mrYes - else - DoPost := False; - if DoPost then - TCustomVirtualStringTree(Owner).EndEditNode + if Modified then begin + if FClosingByCancelButton then + ModalResult := mrCancel + else if FClosingByApplyButton then + ModalResult := mrYes + else + ModalResult := MessageDialog(_('Apply modifications?'), mtConfirmation, [mbYes, mbNo]); + end else - TCustomVirtualStringTree(Owner).CancelEditNode; + ModalResult := mrCancel; end; procedure TfrmTextEditor.btnApplyClick(Sender: TObject); begin - FStopping := True; - TCustomVirtualStringTree(Owner).EndEditNode; + FClosingByApplyButton := True; + Close; end; diff --git a/source/theme_preview.pas b/source/theme_preview.pas index 7208a789c..0ee317f3b 100644 --- a/source/theme_preview.pas +++ b/source/theme_preview.pas @@ -107,7 +107,7 @@ procedure TfrmThemePreview.DownloadProgress(Sender: TObject); if FLastStatusUpdate > GetTickCount-200 then Exit; Download := Sender as THttpDownload; - StatusBarMain.SimpleText := f_('Downloading: %s / %s', [FormatByteNumber(Download.BytesRead), FormatByteNumber(Download.ContentLength)]) + ' ...'; + StatusBarMain.SimpleText := f_('Downloading: %s', [FormatByteNumber(Download.BytesRead)]) + ' ...'; FLastStatusUpdate := GetTickCount; end; diff --git a/source/trigger_editor.pas b/source/trigger_editor.pas index a086b6541..6724e1fd8 100644 --- a/source/trigger_editor.pas +++ b/source/trigger_editor.pas @@ -66,8 +66,6 @@ constructor TfrmTriggerEditor.Create(AOwner: TComponent); inherited; SynMemoBody.Highlighter := Mainform.SynSQLSynUsed; editName.MaxLength := NAME_LEN; - comboTiming.Items.Text := 'BEFORE'+CRLF+'AFTER'; - comboEvent.Items.Text := 'INSERT'+CRLF+'UPDATE'+CRLF+'DELETE'; for i:=0 to Mainform.SynCompletionProposal.Columns.Count-1 do begin col := SynCompletionProposalStatement.Columns.Add; col.ColumnWidth := Mainform.SynCompletionProposal.Columns[i].ColumnWidth; @@ -88,7 +86,7 @@ procedure TfrmTriggerEditor.Init(Obj: TDBObject); DBObjects: TDBObjectList; i: Integer; Found: Boolean; - Body: String; + Body, QuoteCharsRx, QuotedWordRx: String; rx: TRegExpr; begin inherited; @@ -97,7 +95,14 @@ procedure TfrmTriggerEditor.Init(Obj: TDBObject); comboDefiner.TextHint := f_('Current user (%s)', [Obj.Connection.CurrentUserHostCombination]); comboDefiner.Hint := f_('Leave empty for current user (%s)', [Obj.Connection.CurrentUserHostCombination]); SynMemoBody.Text := 'BEGIN'+CRLF+CRLF+'END'; + comboEvent.Items.Text := 'INSERT'+CRLF+'UPDATE'+CRLF+'DELETE'; comboEvent.ItemIndex := 0; + case Obj.Connection.Parameters.NetTypeGroup of + ngSQLite: + comboTiming.Items.Text := 'BEFORE' + sLineBreak + 'AFTER' + sLineBreak + 'INSTEAD OF'; + else + comboTiming.Items.Text := 'BEFORE' + sLineBreak + 'AFTER'; + end; comboTiming.ItemIndex := 0; DBObjects := MainForm.ActiveConnection.GetDBObjects(Mainform.ActiveDatabase); comboTable.Items.Clear; @@ -107,51 +112,44 @@ procedure TfrmTriggerEditor.Init(Obj: TDBObject); end; if comboTable.Items.Count > 0 then comboTable.ItemIndex := 0; + if ObjectExists then begin // Edit mode editName.Text := DBObject.Name; - Definitions := MainForm.ActiveConnection.GetResults('SHOW TRIGGERS FROM '+Obj.Connection.QuoteIdent(Mainform.ActiveDatabase)); - Found := False; - while not Definitions.Eof do begin - if Definitions.Col('Trigger') = DBObject.Name then begin - // "Definer" column available since MySQL 5.0.17 - comboDefiner.Text := Definitions.Col('Definer', True); - comboTable.ItemIndex := comboTable.Items.IndexOf(Definitions.Col('Table')); - comboTiming.ItemIndex := comboTiming.Items.IndexOf(UpperCase(Definitions.Col('Timing'))); - comboEvent.ItemIndex := comboEvent.Items.IndexOf(UpperCase(Definitions.Col('Event'))); - // "Statement" column from SHOW TRIGGERS does not escape single quotes where required. - // See http://www.heidisql.com/forum.php?t=16501 - // But SHOW CREATE TRIGGER was introduced in MySQL 5.1.21 - // See http://www.heidisql.com/forum.php?t=16662 - if DBObject.Connection.ServerVersionInt < 50121 then begin - Body := Definitions.Col('Statement'); - end else begin - rx := TRegExpr.Create; - rx.ModifierI := True; - rx.Expression := 'FOR\s+EACH\s+ROW\s+(.+)$'; - try - Body := DBObject.Connection.GetCreateCode(DBObject); - if rx.Exec(Body) then - Body := rx.Match[1] - else - raise EDbError.CreateFmt(_('Result from previous query does not contain expected pattern: %s'), [rx.Expression]); - except - on E:EDbError do begin - DBObject.Connection.Log(lcError, E.Message); - Body := Definitions.Col('Statement'); - end; - end; - end; - SynMemoBody.Text := Body; - SynMemoBody.TopLine := FMainSynMemoPreviousTopLine; - Found := True; - break; + + // MariaDB: CREATE DEFINER=`root`@`localhost` TRIGGER `trg` BEFORE INSERT ON `tbl` FOR EACH ROW BEGIN .. END + // SQLite: CREATE TRIGGER "test_delete" AFTER INSERT ON "albums" FOR EACH ROW BEGIN .. END + rx := TRegExpr.Create; + rx.ModifierI := True; + QuoteCharsRx := QuoteRegExprMetaChars(DBObject.Connection.QuoteChars); + QuotedWordRx := '['+QuoteCharsRx+']?[^'+QuoteCharsRx+']+['+QuoteCharsRx+']?'; + rx.Expression := '(\sDEFINER=('+QuotedWordRx+'@'+QuotedWordRx+'))?' + + '\s+TRIGGER\s+'+QuotedWordRx + + '\s+('+Implode('|', comboTiming.Items)+')' + + '\s+('+Implode('|', comboEvent.Items)+')' + + '\s+ON\s+('+QuotedWordRx+')' + + '\s+FOR\s+EACH\s+ROW\s+(.+)$'; + try + Body := DBObject.Connection.GetCreateCode(DBObject); + if rx.Exec(Body) then begin + comboDefiner.Text := DBObject.Connection.DeQuoteIdent(rx.Match[2], '@'); + comboTiming.ItemIndex := comboTiming.Items.IndexOf(UpperCase(rx.Match[3])); + comboEvent.ItemIndex := comboEvent.Items.IndexOf(UpperCase(rx.Match[4])); + comboTable.ItemIndex := comboTable.Items.IndexOf(DBObject.Connection.DeQuoteIdent(rx.Match[5])); + Body := rx.Match[6]; + end + else + raise EDbError.CreateFmt(_('Result from previous query does not contain expected pattern: %s'), [rx.Expression]); + except + on E:EDbError do begin + DBObject.Connection.Log(lcError, E.Message); + Body := ''; end; - Definitions.Next; end; - FreeAndNil(Definitions); - if not Found then - Raise Exception.Create(_('Trigger definition not found!')); + + SynMemoBody.Text := Body; + SynMemoBody.TopLine := FMainSynMemoPreviousTopLine; + end else begin editName.Text := ''; if MainForm.FocusedTables.Count > 0 then begin @@ -164,6 +162,7 @@ procedure TfrmTriggerEditor.Init(Obj: TDBObject); end; end; end; + // Buttons are randomly moved, since VirtualTree update, see #440 btnSave.Top := Height - btnSave.Height - 3; btnHelp.Top := btnSave.Top; @@ -227,6 +226,8 @@ procedure TfrmTriggerEditor.comboDefinerDropDown(Sender: TObject); function TfrmTriggerEditor.ApplyModifications: TModalResult; +var + OldCreateCode: String; begin // Edit mode means we drop the trigger and recreate it, as there is no ALTER TRIGGER. Result := mrOk; @@ -236,7 +237,9 @@ function TfrmTriggerEditor.ApplyModifications: TModalResult; // So, we take the risk of loosing the trigger for cases in which the user has SQL errors in // his statement. The user must fix such errors and re-press "Save" while we have them in memory, // otherwise the trigger attributes are lost forever. + OldCreateCode := ''; if ObjectExists then try + OldCreateCode := DBObject.CreateCode; DBObject.Connection.Query('DROP TRIGGER '+DBObject.Connection.QuoteIdent(DBObject.Name)); except end; @@ -252,6 +255,8 @@ function TfrmTriggerEditor.ApplyModifications: TModalResult; on E:EDbError do begin ErrorDialog(E.Message); Result := mrAbort; + if not OldCreateCode.IsEmpty then + DBObject.Connection.Query(OldCreateCode); end; end; end; @@ -311,3 +316,4 @@ function TfrmTriggerEditor.ComposeCreateStatement: String; end. + diff --git a/source/updatecheck.pas b/source/updatecheck.pas index 04584422f..8da84a78d 100644 --- a/source/updatecheck.pas +++ b/source/updatecheck.pas @@ -42,6 +42,7 @@ TfrmUpdateCheck = class(TExtForm) procedure DownloadProgress(Sender: TObject); function GetLinkUrl(Sender: TObject; LinkType: String): String; function GetTaskXmlFileContents: String; + function AppDirIsWritable: Boolean; public { Public declarations } BuildRevision: Integer; @@ -164,7 +165,10 @@ procedure TfrmUpdateCheck.ReadCheckFile; ReleasePackage := IfThen(AppSettings.PortableMode, 'portable', 'installer'); memoRelease.Lines.Add(f_('Version %s (yours: %s)', [ReleaseVersion, Mainform.AppVersion])); memoRelease.Lines.Add(f_('Released: %s', [Ini.ReadString(INISECT_RELEASE, 'Date', '')])); - Note := Ini.ReadString(INISECT_RELEASE, 'Note', ''); + if IsWine then + Note := _('Wine support is deprecated. Future versions will not work reliably. Use the native Linux or macOS releases instead.') + else + Note := Ini.ReadString(INISECT_RELEASE, 'Note', ''); if Note <> '' then memoRelease.Lines.Add(_('Notes') + ': ' + Note); @@ -207,9 +211,10 @@ procedure TfrmUpdateCheck.ReadCheckFile; if btnBuild.Enabled then begin TaskXmlFile := GetTempDir + APPNAME + '_task_restart.xml'; - SaveUnicodeFile(TaskXmlFile, GetTaskXmlFileContents); + SaveUnicodeFile(TaskXmlFile, GetTaskXmlFileContents, UTF8NoBOMEncoding); FRestartTaskName := ValidFilename(ParamStr(0)); ShellExec('schtasks', '', '/Create /TN "'+FRestartTaskName+'" /xml '+TaskXmlFile, True); + btnBuild.ElevationRequired := not AppDirIsWritable; end; end; @@ -236,7 +241,7 @@ procedure TfrmUpdateCheck.LinkLabelReleaseLinkClick(Sender: TObject; Close; end else if Link = SLinkInstructionsPortable then begin - MessageDialog(f_('Download the portable package and extract it in %s', [ExtractFilePath(Application.ExeName)]), mtInformation, [mbOK]); + MessageDialog(f_('Download the portable package and extract it in %s', [GetAppDir]), mtInformation, [mbOK]); end; end; @@ -290,8 +295,8 @@ procedure TfrmUpdateCheck.btnBuildClick(Sender: TObject); if not FileExists(DownloadFilename) then Raise Exception.CreateFmt(_('Downloaded file not found: %s'), [DownloadFilename]); BuildSizeDownloaded := _GetFileSize(DownloadFilename); - if (Download.ContentLength > 0) and (BuildSizeDownloaded < Download.ContentLength) then - Raise Exception.CreateFmt(_('Downloaded file corrupted: %s (Size is %d and should be %d)'), [DownloadFilename, BuildSizeDownloaded, Download.ContentLength]); + if BuildSizeDownloaded < SIZE_MB then + Raise Exception.CreateFmt(_('Downloaded file corrupted: %s (Size is %d / too small)'), [DownloadFilename, BuildSizeDownloaded]); Status(_('Update in progress')+' ...'); ResInfoblockHandle := FindResource(HInstance, 'UPDATER', 'EXE'); @@ -342,7 +347,7 @@ procedure TfrmUpdateCheck.DownloadProgress(Sender: TObject); if FLastStatusUpdate > GetTickCount-200 then Exit; Download := Sender as THttpDownload; - Status(f_('Downloading: %s / %s', [FormatByteNumber(Download.BytesRead), FormatByteNumber(Download.ContentLength)]) + ' ...'); + Status(f_('Downloading: %s', [FormatByteNumber(Download.BytesRead)]) + ' ...'); FLastStatusUpdate := GetTickCount; end; @@ -416,7 +421,7 @@ function TfrmUpdateCheck.GetTaskXmlFileContents: String; ' ' + sLineBreak + ' ' + sLineBreak + ' ' + sLineBreak + - ' ' + ParamStr(0) + '' + sLineBreak + + ' "' + ParamStr(0) + '"' + sLineBreak + ' --runfrom=scheduler' + sLineBreak + ' ' + sLineBreak + ' ' + sLineBreak + @@ -424,6 +429,21 @@ function TfrmUpdateCheck.GetTaskXmlFileContents: String; end; +function TfrmUpdateCheck.AppDirIsWritable: Boolean; +var + TestFile: string; + H: THandle; +begin + TestFile := IncludeTrailingPathDelimiter(GetAppDir) + 'chk.tmp'; + H := CreateFile(PChar(TestFile), GENERIC_READ or GENERIC_WRITE, 0, nil, + CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0); + Result := H <> INVALID_HANDLE_VALUE; + if Result then + CloseHandle(H); + DeleteFile(TestFile); +end; + + procedure DeleteRestartTask; begin // TN = Task Name diff --git a/source/usermanager.dfm b/source/usermanager.dfm index d0e01cc2e..56631a43d 100644 --- a/source/usermanager.dfm +++ b/source/usermanager.dfm @@ -17,7 +17,6 @@ object UserManagerForm: TUserManagerForm OnClose = FormClose OnCloseQuery = FormCloseQuery OnCreate = FormCreate - OnResize = FormResize OnShow = FormShow DesignSize = ( 484 @@ -35,7 +34,6 @@ object UserManagerForm: TUserManagerForm Margins.Right = 0 Margins.Bottom = 40 ResizeStyle = rsUpdate - OnMoved = FormResize end object lblWarning: TLabel Left = 8 @@ -94,18 +92,20 @@ object UserManagerForm: TUserManagerForm Left = 3 Top = 3 Width = 171 - Height = 13 + Height = 14 Align = alTop Caption = '&Select user account:' FocusControl = listUsers + ExplicitWidth = 113 end object listUsers: TVirtualStringTree Left = 0 - Top = 41 + Top = 64 Width = 177 - Height = 275 + Height = 252 Align = alClient Header.AutoSizeIndex = 0 + Header.Height = 18 Header.Options = [hoAutoResize, hoColumnResize, hoDblClickResize, hoDrag, hoHotTrack, hoShowSortGlyphs, hoVisible, hoDisableAnimatedResize, hoAutoResizeInclCaption] Header.PopupMenu = MainForm.popupListHeader Header.SortColumn = 0 @@ -131,21 +131,25 @@ object UserManagerForm: TUserManagerForm item Position = 0 Text = 'Username' - Width = 93 + Width = 43 end item Position = 1 Text = 'Host' Width = 80 + end + item + Position = 2 + Text = 'Plugin' end> end object ToolBar1: TToolBar Left = 0 - Top = 19 + Top = 20 Width = 177 Height = 22 AutoSize = True - ButtonWidth = 58 + ButtonWidth = 63 Caption = 'ToolBar1' Images = MainForm.VirtualImageListMain List = True @@ -156,20 +160,21 @@ object UserManagerForm: TUserManagerForm Left = 0 Top = 0 Caption = 'Add' + DropdownMenu = menuAdd ImageIndex = 45 ImageName = 'icons8-add' - OnClick = btnAddUserClick + Style = tbsWholeDropDown end object btnCloneUser: TToolButton - Left = 58 + Left = 72 Top = 0 Caption = 'Clone' ImageIndex = 3 ImageName = 'icons8-copy-100' - OnClick = btnAddUserClick + OnClick = menuItemUserClick end object btnDeleteUser: TToolButton - Left = 116 + Left = 135 Top = 0 Caption = 'Delete' ImageIndex = 46 @@ -177,6 +182,21 @@ object UserManagerForm: TUserManagerForm OnClick = btnDeleteUserClick end end + object editFilterUsers: TButtonedEdit + Left = 0 + Top = 42 + Width = 177 + Height = 22 + Align = alTop + Images = MainForm.VirtualImageListMain + LeftButton.ImageIndex = 30 + LeftButton.Visible = True + RightButton.ImageIndex = 193 + TabOrder = 2 + TextHint = 'Filter ...' + OnChange = editFilterUsersChange + OnRightButtonClick = editFilterUsersRightButtonClick + end end object pnlRight: TPanel AlignWithMargins = True @@ -192,71 +212,11 @@ object UserManagerForm: TUserManagerForm BevelOuter = bvNone Constraints.MinWidth = 20 TabOrder = 1 - object tlbObjects: TToolBar - Left = 0 - Top = 145 - Width = 283 - Height = 22 - AutoSize = True - ButtonWidth = 79 - Caption = 'tlbObjects' - Images = MainForm.VirtualImageListMain - List = True - ParentShowHint = False - ShowCaptions = True - ShowHint = True - TabOrder = 1 - Wrapable = False - object lblAllowAccessTo: TLabel - Left = 0 - Top = 0 - Width = 121 - Height = 22 - AutoSize = False - Caption = 'Allow access to:' - Transparent = False - Layout = tlCenter - end - object btnAddObject: TToolButton - Left = 121 - Top = 0 - Hint = 'Add object ...' - Caption = 'Add object' - ImageIndex = 45 - ImageName = 'icons8-add' - OnClick = btnAddObjectClick - end - end - object treePrivs: TVirtualStringTree - Left = 0 - Top = 167 - Width = 283 - Height = 149 - Align = alClient - Header.AutoSizeIndex = 0 - Header.MainColumn = -1 - Images = MainForm.VirtualImageListMain - IncrementalSearch = isAll - TabOrder = 2 - TreeOptions.AutoOptions = [toAutoDropExpand, toAutoTristateTracking, toAutoDeleteMovedNodes, toAutoChangeScale] - TreeOptions.MiscOptions = [toAcceptOLEDrop, toCheckSupport, toFullRepaintOnResize, toInitOnSave, toToggleOnDblClick, toWheelPanning, toEditOnClick] - TreeOptions.PaintOptions = [toShowButtons, toShowDropmark, toShowRoot, toShowTreeLines, toThemeAware, toUseBlendedImages, toUseExplorerTheme, toHideTreeLinesIfThemed] - OnChecked = treePrivsChecked - OnExpanded = treePrivsExpanded - OnGetText = treePrivsGetText - OnPaintText = treePrivsPaintText - OnGetImageIndex = treePrivsGetImageIndex - OnInitChildren = treePrivsInitChildren - OnInitNode = treePrivsInitNode - Touch.InteractiveGestures = [igPan, igPressAndTap] - Touch.InteractiveGestureOptions = [igoPanSingleFingerHorizontal, igoPanSingleFingerVertical, igoPanInertia, igoPanGutter, igoParentPassthrough] - Columns = <> - end object PageControlSettings: TPageControl Left = 0 Top = 0 Width = 283 - Height = 145 + Height = 205 ActivePage = tabCredentials Align = alTop TabOrder = 0 @@ -264,43 +224,57 @@ object UserManagerForm: TUserManagerForm Caption = 'Credentials' DesignSize = ( 275 - 117) + 176) object lblUsername: TLabel Left = 3 Top = 10 - Width = 55 - Height = 13 + Width = 62 + Height = 14 Caption = 'User &name:' end object lblFromHost: TLabel Left = 3 Top = 37 - Width = 52 - Height = 13 + Width = 59 + Height = 14 Caption = 'From &host:' FocusControl = editFromHost end object lblPassword: TLabel Left = 3 Top = 64 - Width = 50 - Height = 13 + Width = 55 + Height = 14 Caption = '&Password:' FocusControl = editPassword end object lblRepeatPassword: TLabel Left = 3 Top = 91 - Width = 88 - Height = 13 + Width = 98 + Height = 14 Caption = 'Repeat password:' FocusControl = editRepeatPassword end + object lblDefaultRole: TLabel + Left = 3 + Top = 147 + Width = 67 + Height = 14 + Caption = 'Default role:' + end + object lblPlugin: TLabel + Left = 3 + Top = 120 + Width = 36 + Height = 14 + Caption = 'Plugin:' + end object editRepeatPassword: TEdit Left = 176 Top = 88 Width = 96 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] PasswordChar = '*' TabOrder = 3 @@ -310,7 +284,7 @@ object UserManagerForm: TUserManagerForm Left = 176 Top = 61 Width = 96 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain PasswordChar = '*' @@ -325,7 +299,7 @@ object UserManagerForm: TUserManagerForm Left = 176 Top = 34 Width = 96 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] Images = MainForm.VirtualImageListMain RightButton.DropDownMenu = menuHost @@ -338,51 +312,71 @@ object UserManagerForm: TUserManagerForm Left = 176 Top = 7 Width = 96 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] TabOrder = 0 OnChange = Modification end + object comboDefaultRole: TComboBox + Left = 176 + Top = 144 + Width = 96 + Height = 22 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + TabOrder = 5 + OnChange = Modification + end + object comboPlugins: TComboBox + Left = 176 + Top = 116 + Width = 96 + Height = 22 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + TabOrder = 4 + OnChange = Modification + end end object tabLimitations: TTabSheet Caption = 'Limitations' ImageIndex = 1 DesignSize = ( 275 - 117) + 176) object lblMaxQueries: TLabel Left = 3 Top = 10 - Width = 85 - Height = 13 + Width = 96 + Height = 14 Caption = 'Queries per hour:' end object lblMaxUpdates: TLabel Left = 3 Top = 37 - Width = 88 - Height = 13 + Width = 100 + Height = 14 Caption = 'Updates per hour:' end object lblMaxConnections: TLabel Left = 3 Top = 64 - Width = 107 - Height = 13 + Width = 122 + Height = 14 Caption = 'Connections per hour:' end object lblMaxUserConnections: TLabel Left = 3 Top = 91 - Width = 127 - Height = 13 + Width = 146 + Height = 14 Caption = 'Simultaneous connections:' end object editMaxQueries: TEdit Left = 176 Top = 7 Width = 80 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] NumbersOnly = True TabOrder = 0 @@ -393,7 +387,7 @@ object UserManagerForm: TUserManagerForm Left = 176 Top = 34 Width = 80 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] NumbersOnly = True TabOrder = 2 @@ -404,7 +398,7 @@ object UserManagerForm: TUserManagerForm Left = 176 Top = 61 Width = 80 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] NumbersOnly = True TabOrder = 4 @@ -415,7 +409,7 @@ object UserManagerForm: TUserManagerForm Left = 176 Top = 88 Width = 80 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] NumbersOnly = True TabOrder = 6 @@ -426,7 +420,7 @@ object UserManagerForm: TUserManagerForm Left = 256 Top = 7 Width = 17 - Height = 21 + Height = 22 Anchors = [akTop, akRight] Associate = editMaxQueries Max = 2147483647 @@ -438,7 +432,7 @@ object UserManagerForm: TUserManagerForm Left = 256 Top = 34 Width = 17 - Height = 21 + Height = 22 Anchors = [akTop, akRight] Associate = editMaxUpdates Max = 2147483647 @@ -449,7 +443,7 @@ object UserManagerForm: TUserManagerForm Left = 256 Top = 61 Width = 17 - Height = 21 + Height = 22 Anchors = [akTop, akRight] Associate = editMaxConnections Max = 2147483647 @@ -460,7 +454,7 @@ object UserManagerForm: TUserManagerForm Left = 256 Top = 88 Width = 17 - Height = 21 + Height = 22 Anchors = [akTop, akRight] Associate = editMaxUserConnections Max = 2147483647 @@ -473,43 +467,43 @@ object UserManagerForm: TUserManagerForm ImageIndex = 2 DesignSize = ( 275 - 117) + 176) object lblCipher: TLabel Left = 3 Top = 36 - Width = 35 - Height = 13 + Width = 38 + Height = 14 Caption = '&Cipher:' FocusControl = editCipher end object lblIssuer: TLabel Left = 3 Top = 62 - Width = 34 - Height = 13 + Width = 36 + Height = 14 Caption = '&Issuer:' FocusControl = editIssuer end object lblSubject: TLabel Left = 3 Top = 89 - Width = 40 - Height = 13 + Width = 46 + Height = 14 Caption = '&Subject:' FocusControl = editSubject end object lblSSL: TLabel Left = 3 Top = 9 - Width = 61 - Height = 13 + Width = 69 + Height = 14 Caption = '&Require SSL:' end object editCipher: TEdit Left = 176 Top = 33 Width = 96 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] TabOrder = 1 Text = 'editCipher' @@ -519,7 +513,7 @@ object UserManagerForm: TUserManagerForm Left = 176 Top = 59 Width = 96 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] TabOrder = 2 Text = 'editIssuer' @@ -529,7 +523,7 @@ object UserManagerForm: TUserManagerForm Left = 176 Top = 86 Width = 96 - Height = 21 + Height = 22 Anchors = [akLeft, akTop, akRight] TabOrder = 3 Text = 'editSubject' @@ -539,7 +533,7 @@ object UserManagerForm: TUserManagerForm Left = 176 Top = 6 Width = 96 - Height = 21 + Height = 22 Style = csDropDownList Anchors = [akLeft, akTop, akRight] TabOrder = 0 @@ -552,6 +546,91 @@ object UserManagerForm: TUserManagerForm end end end + object PageControlAccess: TPageControl + Left = 0 + Top = 205 + Width = 283 + Height = 111 + ActivePage = tabPrivileges + Align = alClient + TabOrder = 1 + object tabPrivileges: TTabSheet + Caption = 'Privileges' + object treePrivs: TVirtualStringTree + Left = 0 + Top = 22 + Width = 275 + Height = 60 + Align = alClient + Header.AutoSizeIndex = 0 + Header.Height = 14 + Header.MainColumn = -1 + Images = MainForm.VirtualImageListMain + IncrementalSearch = isAll + TabOrder = 0 + TreeOptions.AutoOptions = [toAutoDropExpand, toAutoTristateTracking, toAutoDeleteMovedNodes, toAutoChangeScale] + TreeOptions.MiscOptions = [toAcceptOLEDrop, toCheckSupport, toFullRepaintOnResize, toInitOnSave, toToggleOnDblClick, toWheelPanning, toEditOnClick] + TreeOptions.PaintOptions = [toShowButtons, toShowDropmark, toShowRoot, toShowTreeLines, toThemeAware, toUseBlendedImages, toUseExplorerTheme, toHideTreeLinesIfThemed] + OnChecked = treePrivsChecked + OnExpanded = treePrivsExpanded + OnGetText = treePrivsGetText + OnPaintText = treePrivsPaintText + OnGetImageIndex = treePrivsGetImageIndex + OnInitChildren = treePrivsInitChildren + OnInitNode = treePrivsInitNode + Touch.InteractiveGestures = [igPan, igPressAndTap] + Touch.InteractiveGestureOptions = [igoPanSingleFingerHorizontal, igoPanSingleFingerVertical, igoPanInertia, igoPanGutter, igoParentPassthrough] + Columns = <> + end + object tlbObjects: TToolBar + Left = 0 + Top = 0 + Width = 275 + Height = 22 + AutoSize = True + ButtonWidth = 88 + Caption = 'tlbObjects' + Images = MainForm.VirtualImageListMain + List = True + ParentShowHint = False + ShowCaptions = True + ShowHint = True + TabOrder = 1 + Wrapable = False + object btnAddObject: TToolButton + Left = 0 + Top = 0 + Hint = 'Add object ...' + Caption = 'Add object' + ImageIndex = 45 + ImageName = 'icons8-add' + OnClick = btnAddObjectClick + end + end + end + object tabRoles: TTabSheet + Caption = 'Roles' + ImageIndex = 1 + object ValueListEditorRoles: TValueListEditor + Left = 0 + Top = 0 + Width = 275 + Height = 82 + Align = alClient + Strings.Strings = ( + 'Roll=off') + TabOrder = 0 + TitleCaptions.Strings = ( + 'Role name' + 'Assigned') + OnGetPickList = ValueListEditorRolesGetPickList + OnSetEditText = ValueListEditorRolesSetEditText + ColWidths = ( + 150 + 119) + end + end + end end object btnDiscard: TButton Left = 280 @@ -639,4 +718,18 @@ object UserManagerForm: TUserManagerForm end end end + object menuAdd: TPopupMenu + Images = MainForm.VirtualImageListMain + OnPopup = menuAddPopup + Left = 80 + Top = 280 + object menuItemUser: TMenuItem + Caption = 'User' + OnClick = menuItemUserClick + end + object menuItemRole: TMenuItem + Caption = 'Role' + OnClick = menuItemRoleClick + end + end end diff --git a/source/usermanager.pas b/source/usermanager.pas index 8fef0413a..64d8f5239 100644 --- a/source/usermanager.pas +++ b/source/usermanager.pas @@ -6,23 +6,14 @@ interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ToolWin, Vcl.ClipBrd, System.Generics.Collections, System.Generics.Defaults, SynRegExpr, extra_controls, - dbconnection, dbstructures, dbstructures.mysql, apphelpers, VirtualTrees, Vcl.Menus, gnugettext; + dbconnection, dbstructures, dbstructures.mysql, apphelpers, VirtualTrees.BaseTree, VirtualTrees.Types, VirtualTrees, VirtualTrees.EditLink, Vcl.Menus, gnugettext, + VirtualTrees.BaseAncestorVCL, VirtualTrees.AncestorVCL, StrUtils, Vcl.Grids, + Vcl.ValEdit; {$I const.inc} type - TUserProblem = (upNone, upEmptyPassword, upInvalidPasswordLen, upSkipNameResolve, upUnknown); - - TUser = class(TObject) - Username, Host, Password, Cipher, Issuer, Subject: String; - MaxQueries, MaxUpdates, MaxConnections, MaxUserConnections, SSL: Integer; - Problem: TUserProblem; - function HostRequiresNameResolve: Boolean; - end; - PUser = ^TUser; - TUserList = TObjectList; - TPrivObj = class(TObject) GrantCode: String; DBObj: TDBObject; @@ -38,18 +29,44 @@ TPrivComparer = class(TComparer) function Compare(const Left, Right: TPrivObj): Integer; override; end; + TUserProblem = (upNone, upEmptyPassword, upInvalidPasswordLen, upSkipNameResolve, upUnknown); + + TUser = class(TObject) + Username, Host, Password, Cipher, Issuer, Subject, DefaultRole, Plugin: String; + MaxQueries, MaxUpdates, MaxConnections, MaxUserConnections, SSL: Integer; + Problem: TUserProblem; + IsRole: Boolean; + Roles: TStringList; + public + class var RoleNo: String; + class var RoleYes: String; + class var RoleYesAdmin: String; + constructor Create; + destructor Destroy; override; + function HostRequiresNameResolve: Boolean; + procedure ParseSettings(GrantOrCreate: String; Priv: TPrivObj); + function IsUser: Boolean; + function AssignedRolesCount: Integer; + end; + PUser = ^TUser; + TUserList = class(TObjectList) + public + function GetRoleNames: TStringList; overload; + procedure GetRoleNames(Strings: TStrings); overload; + function GetDefaultRoles(ExcludeRole: TUser): TStringList; + end; + EInputError = class(Exception); TUserManagerForm = class(TExtForm) btnCancel: TButton; btnSave: TButton; + comboPlugins: TComboBox; + lblPlugin: TLabel; pnlLeft: TPanel; listUsers: TVirtualStringTree; Splitter1: TSplitter; pnlRight: TPanel; - tlbObjects: TToolBar; - btnAddObject: TToolButton; - treePrivs: TVirtualStringTree; btnDiscard: TButton; lblUsers: TLabel; ToolBar1: TToolBar; @@ -57,7 +74,6 @@ TUserManagerForm = class(TExtForm) btnDeleteUser: TToolButton; btnCloneUser: TToolButton; lblWarning: TLabel; - lblAllowAccessTo: TLabel; menuHost: TPopupMenu; menuHost1: TMenuItem; menuHostLocal4: TMenuItem; @@ -107,9 +123,22 @@ TUserManagerForm = class(TExtForm) editSubject: TEdit; comboSSL: TComboBox; lblSSL: TLabel; + editFilterUsers: TButtonedEdit; + menuAdd: TPopupMenu; + menuItemUser: TMenuItem; + menuItemRole: TMenuItem; + PageControlAccess: TPageControl; + tabPrivileges: TTabSheet; + tabRoles: TTabSheet; + treePrivs: TVirtualStringTree; + tlbObjects: TToolBar; + btnAddObject: TToolButton; + ValueListEditorRoles: TValueListEditor; + lblDefaultRole: TLabel; + comboDefaultRole: TComboBox; procedure FormCreate(Sender: TObject); procedure FormShow(Sender: TObject); - procedure btnAddUserClick(Sender: TObject); + procedure menuItemUserClick(Sender: TObject); procedure btnDeleteUserClick(Sender: TObject); procedure listUsersFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex); @@ -152,18 +181,31 @@ TUserManagerForm = class(TExtForm) procedure listUsersHotChange(Sender: TBaseVirtualTree; OldNode, NewNode: PVirtualNode); procedure udMaxQueriesClick(Sender: TObject; Button: TUDBtnType); procedure comboSSLChange(Sender: TObject); - procedure FormResize(Sender: TObject); + procedure editFilterUsersRightButtonClick(Sender: TObject); + procedure editFilterUsersChange(Sender: TObject); + procedure menuItemRoleClick(Sender: TObject); + procedure menuAddPopup(Sender: TObject); + procedure ValueListEditorRolesGetPickList(Sender: TObject; + const KeyName: string; Values: TStrings); + procedure ValueListEditorRolesSetEditText(Sender: TObject; ACol, + ARow: LongInt; const Value: string); private { Private declarations } FUsers: TUserList; FModified, FAdded: Boolean; + FHasIsRole, FHasDefaultRole: Boolean; + FHasPlugin: Boolean; + FPlugins: TStringList; FCloneGrants: TStringList; FPrivObjects: TPrivObjList; FPrivsGlobal, FPrivsDb, FPrivsTable, FPrivsRoutine, FPrivsColumn: TStringList; FConnection: TDBConnection; + FColorReadPriv, FColorWritePriv, FColorAdminPriv: TColor; + FSQLPluginPrefix, FSQLPluginPassPrefix: String; procedure SetModified(Value: Boolean); property Modified: Boolean read FModified write SetModified; function GetPrivByNode(Node: PVirtualNode): TPrivObj; + function SelectUserNode(User: TUser): Boolean; public { Public declarations } end; @@ -210,17 +252,16 @@ procedure TUserManagerForm.FormCreate(Sender: TObject); lblWarning.Font.Color := clRed; PrivsRead := Explode(',', 'SELECT,SHOW VIEW,SHOW DATABASES,PROCESS,EXECUTE'); PrivsWrite := Explode(',', 'ALTER,CREATE,DROP,DELETE,UPDATE,INSERT,ALTER ROUTINE,CREATE ROUTINE,CREATE TEMPORARY TABLES,'+ - 'CREATE VIEW,INDEX,TRIGGER,EVENT,REFERENCES,CREATE TABLESPACE'); + 'CREATE VIEW,INDEX,TRIGGER,EVENT,REFERENCES,CREATE TABLESPACE,DELETE HISTORY'); PrivsAdmin := Explode(',', 'RELOAD,SHUTDOWN,REPLICATION CLIENT,REPLICATION SLAVE,SUPER,LOCK TABLES,GRANT,FILE,CREATE USER,'+ 'BINLOG ADMIN,BINLOG REPLAY,CONNECTION ADMIN,FEDERATED ADMIN,READ_ONLY ADMIN,REPLICATION MASTER ADMIN,'+ - 'REPLICATION SLAVE ADMIN,SET USER'); -end; - - -procedure TUserManagerForm.FormResize(Sender: TObject); -begin - // Manually right align "Add object" button - lblAllowAccessTo.Width := pnlRight.Width - btnAddObject.Width; + 'REPLICATION SLAVE ADMIN,SET USER,SLAVE MONITOR'); + FixVT(listUsers); + FixVT(treePrivs); + FHasIsRole := False; + FHasDefaultRole := False; + menuItemUser.ImageIndex := ICONINDEX_USER; + menuItemRole.ImageIndex := ICONINDEX_ROLE; end; @@ -229,11 +270,10 @@ procedure TUserManagerForm.FormShow(Sender: TObject); Version, i: Integer; Users: TDBQuery; U: TUser; - tmp, PasswordExpr: String; + tmp, PasswordExpr, IsRoleExpr, DefaultRoleExpr, PluginExpr: String; SkipNameResolve, - HasPassword, - HasAuthString, - PasswordLengthMatters: Boolean; + HasPassword, HasAuthString: Boolean; + PasswordLengthMatters, PasswordLengthValid: Boolean; UserTableColumns: TStringList; function InitPrivList(Values: String): TStringList; @@ -247,9 +287,15 @@ procedure TUserManagerForm.FormShow(Sender: TObject); Width := AppSettings.ReadIntDpiAware(asUsermanagerWindowWidth, Self); Height := AppSettings.ReadIntDpiAware(asUsermanagerWindowHeight, Self); pnlLeft.Width := AppSettings.ReadIntDpiAware(asUsermanagerListWidth, Self); - FixVT(listUsers); - FixVT(treePrivs); RestoreListSetup(listUsers); + FColorReadPriv := clGreen; + FColorWritePriv := clMaroon; + FColorAdminPriv := clNavy; + if ThemeIsDark then begin + FColorReadPriv := ColorAdjustBrightness(FColorReadPriv, 128); + FColorWritePriv := ColorAdjustBrightness(FColorWritePriv, 128); + FColorAdminPriv := ColorAdjustBrightness(FColorAdminPriv, 128); + end; FConnection := Mainform.ActiveConnection; Version := FConnection.ServerVersionInt; @@ -258,7 +304,8 @@ procedure TUserManagerForm.FormShow(Sender: TObject); FPrivsTable := InitPrivList('ALTER,CREATE,DELETE,DROP,GRANT,INDEX'); FPrivsRoutine := InitPrivList('GRANT'); FPrivsColumn := InitPrivList('INSERT,SELECT,UPDATE,REFERENCES'); - PasswordLengthMatters := True; + FSQLPluginPrefix := IfThen(FConnection.Parameters.IsMariaDB, 'VIA', 'WITH'); + FSQLPluginPassPrefix := IfThen(FConnection.Parameters.IsMariaDB, 'USING', 'BY'); if Version >= 40002 then begin FPrivsGlobal.Add('REPLICATION CLIENT'); @@ -291,24 +338,29 @@ procedure TUserManagerForm.FormShow(Sender: TObject); PrivsDb.Add('PROXY'); end; } - if Version >= 80000 then begin - // MySQL 8 has predefined length of hashed passwords only with - // mysql_native_password plugin enabled users - PasswordLengthMatters := False; - end; // See https://mariadb.com/kb/en/changes-improvements-in-mariadb-105/#privileges-made-more-granular - if FConnection.Parameters.IsMariaDB and (Version > 100502) then begin - i := FPrivsGlobal.IndexOf('REPLICATION CLIENT'); - if i > -1 then - FPrivsGlobal.Delete(i); - FPrivsGlobal.Add('BINLOG ADMIN'); // replaces REPLICATION CLIENT - FPrivsGlobal.Add('BINLOG REPLAY'); - FPrivsGlobal.Add('CONNECTION ADMIN'); - FPrivsGlobal.Add('FEDERATED ADMIN'); - FPrivsGlobal.Add('READ_ONLY ADMIN'); - FPrivsGlobal.Add('REPLICATION MASTER ADMIN'); - FPrivsGlobal.Add('REPLICATION SLAVE ADMIN'); - FPrivsGlobal.Add('SET USER'); + if FConnection.Parameters.IsMariaDB then begin + if Version > 100502 then begin + i := FPrivsGlobal.IndexOf('REPLICATION CLIENT'); + if i > -1 then + FPrivsGlobal.Delete(i); + FPrivsGlobal.Add('BINLOG ADMIN'); // replaces REPLICATION CLIENT + FPrivsGlobal.Add('BINLOG REPLAY'); + FPrivsGlobal.Add('CONNECTION ADMIN'); + FPrivsGlobal.Add('FEDERATED ADMIN'); + FPrivsGlobal.Add('READ_ONLY ADMIN'); + FPrivsGlobal.Add('REPLICATION MASTER ADMIN'); + FPrivsGlobal.Add('REPLICATION SLAVE ADMIN'); + FPrivsGlobal.Add('SET USER'); + end; + if Version >= 100509 then begin + FPrivsGlobal.Add('SLAVE MONITOR'); + end; + if Version >= 100304 then begin + FPrivsGlobal.Add('DELETE HISTORY'); + end; + + end; FPrivsTable.AddStrings(FPrivsColumn); @@ -334,12 +386,13 @@ procedure TUserManagerForm.FormShow(Sender: TObject); tmp := FConnection.GetSessionVariable('skip_name_resolve'); SkipNameResolve := LowerCase(tmp) = 'on'; - FConnection.Query('FLUSH PRIVILEGES'); - // Peek into user table structure, and find out where the password hash is stored UserTableColumns := FConnection.GetCol('SHOW COLUMNS FROM '+FConnection.QuoteIdent('mysql')+'.'+FConnection.QuoteIdent('user')); HasPassword := UserTableColumns.IndexOf('password') > -1; HasAuthString := UserTableColumns.IndexOf('authentication_string') > -1; + FHasIsRole := UserTableColumns.IndexOf('is_role') > -1; + FHasDefaultRole := UserTableColumns.IndexOf('default_role') > -1; + FHasPlugin := UserTableColumns.IndexOf('plugin') > -1; if HasPassword and (not HasAuthString) then PasswordExpr := 'password' else if (not HasPassword) and HasAuthString then @@ -349,32 +402,55 @@ procedure TUserManagerForm.FormShow(Sender: TObject); else Raise Exception.Create(_('No password hash column available')); PasswordExpr := PasswordExpr + ' AS ' + FConnection.QuoteIdent('password'); + IsRoleExpr := IfThen(FHasIsRole, 'is_role', FConnection.EscapeString('N')+' AS is_role'); + DefaultRoleExpr := IfThen(FHasDefaultRole, 'default_role', FConnection.EscapeString('')+' AS default_role'); + PluginExpr := IfThen(FHasPlugin, 'plugin', FConnection.EscapeString('')+' AS plugin'); + if FConnection.SqlProvider.Has(qGetAuthPlugins) then + FPlugins := FConnection.GetCol(FConnection.SqlProvider.GetSql(qGetAuthPlugins)) + else + FPlugins := TStringList.Create; Users := FConnection.GetResults( - 'SELECT '+FConnection.QuoteIdent('user')+', '+FConnection.QuoteIdent('host')+', '+PasswordExpr+' '+ + 'SELECT '+ + FConnection.QuoteIdent('user') + ', ' + + FConnection.QuoteIdent('host') + ', ' + + PasswordExpr + ', ' + + IsRoleExpr + ', ' + + DefaultRoleExpr + ', ' + + PluginExpr + ' ' + 'FROM '+FConnection.QuoteIdent('mysql')+'.'+FConnection.QuoteIdent('user') ); FUsers := TUserList.Create(True); + ValueListEditorRoles.Strings.Clear; while not Users.Eof do begin U := TUser.Create; U.Username := Users.Col('user'); U.Host := Users.Col('host'); U.Password := Users.Col('password'); + U.IsRole := UpperCase(Users.Col('is_role')) = 'Y'; + U.DefaultRole := Users.Col('default_role'); + U.Plugin := Users.Col('plugin'); U.Problem := upNone; - if Length(U.Password) = 0 then - U.Problem := upEmptyPassword; - if PasswordLengthMatters and (not (Length(U.Password) in [0, 16, 41])) then - U.Problem := upInvalidPasswordLen - else if SkipNameResolve and U.HostRequiresNameResolve then - U.Problem := upSkipNameResolve; + if U.IsUser then begin + if Length(U.Password) = 0 then + U.Problem := upEmptyPassword; + PasswordLengthMatters := ExecRegExpr('(mysql_native_password|mysql_old_password)', U.Plugin) or (not FHasPlugin); + PasswordLengthValid := Byte(Length(U.Password)) in [0, 16, 41]; + if PasswordLengthMatters and (not PasswordLengthValid) then + U.Problem := upInvalidPasswordLen + else if SkipNameResolve and U.HostRequiresNameResolve then + U.Problem := upSkipNameResolve; + end; FUsers.Add(U); Users.Next; end; + listUsers.Clear; InvalidateVT(listUsers, VTREE_NOTLOADED, False); FPrivObjects := TPrivObjList.Create(TPrivComparer.Create, True); Modified := False; FAdded := False; + tabRoles.TabVisible := FHasIsRole; listUsers.OnFocusChanged(listUsers, listUsers.FocusedNode, listUsers.FocusedColumn); except on E:EDbError do begin @@ -446,6 +522,20 @@ procedure TUserManagerForm.udMaxQueriesClick(Sender: TObject; Button: TUDBtnType end; +procedure TUserManagerForm.ValueListEditorRolesGetPickList(Sender: TObject; + const KeyName: string; Values: TStrings); +begin + Values.Add(TUser.RoleNo); + Values.Add(TUser.RoleYes); + Values.Add(TUser.RoleYesAdmin); +end; + +procedure TUserManagerForm.ValueListEditorRolesSetEditText(Sender: TObject; + ACol, ARow: LongInt; const Value: string); +begin + Modification(Sender); +end; + procedure TUserManagerForm.listUsersAfterPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); begin // Background painting for sorted column @@ -487,6 +577,7 @@ procedure TUserManagerForm.listUsersFocusChanging(Sender: TBaseVirtualTree; OldN end; mrNo: begin Allowed := True; + Modified := False; if FAdded then btnDeleteUser.Click; end; @@ -501,37 +592,68 @@ procedure TUserManagerForm.listUsersFocusChanged(Sender: TBaseVirtualTree; Node: var P, Ptmp, PCol: TPrivObj; User: PUser; - UserHost, RequireClause, WithClause, Msg: String; - Grants, AllPNames, Cols: TStringList; + UserHost, UserHostRx, Msg, CreateUser, RxQuotes: String; + Grants, AllPNames, Cols, RoleNames, DefaultRoles: TStringList; rxTemp, rxGrant: TRegExpr; i, j: Integer; UserSelected: Boolean; Obj: TDBObject; begin // Parse and display privileges of focused user + listUsers.TrySetFocus; // Steal focus from roles, prevents empty cell bug UserSelected := Assigned(Node); + User := nil; FPrivObjects.Clear; Caption := MainForm.actUserManager.Caption; + // Credentials tab editUsername.Clear; editFromHost.Clear; editPassword.Clear; editPassword.TextHint := ''; editRepeatPassword.Clear; + comboPlugins.Items.Clear; + comboPlugins.Items.Add(_('None')); + comboPlugins.Items.AddStrings(FPlugins); + comboPlugins.ItemIndex := 0; + comboDefaultRole.Items.Clear; + comboDefaultRole.Items.Add(_('None')); + FUsers.GetRoleNames(comboDefaultRole.Items); + comboDefaultRole.ItemIndex := 0; + // Limitations tab udMaxQueries.Position := 0; udMaxUpdates.Position := 0; udMaxConnections.Position := 0; udMaxUserConnections.Position := 0; + // SSL tab comboSSL.ItemIndex := 0; comboSSL.OnChange(Sender); editCipher.Clear; editIssuer.Clear; editSubject.Clear; + // Page control + tabPrivileges.Caption := _('Privileges'); + tabRoles.Caption := _('Roles'); + + // All possible quote chars, escaped for RegExpr. Todo: use in all relevant expressions. + RxQuotes := '['+QuoteRegExprMetaChars(FConnection.QuoteChars + FConnection.StringQuoteChar)+']'; if UserSelected then begin User := Sender.GetNodeData(Node); - UserHost := FConnection.EscapeString(User.Username)+'@'+FConnection.EscapeString(User.Host); + UserHost := FConnection.EscapeString(User.Username); + UserHostRx := RxQuotes + '?' + QuoteRegExprMetaChars(User.Username) + RxQuotes + '?'; + if User.IsUser then begin + UserHost := UserHost + '@' + FConnection.EscapeString(User.Host); + UserHostRx := UserHostRx + '@' + RxQuotes + '?' + QuoteRegExprMetaChars(User.Host) + RxQuotes + '?'; + end; editUsername.Text := User.Username; editFromHost.Text := User.Host; + i := comboPlugins.Items.IndexOf(User.Plugin); + if i > -1 then + comboPlugins.ItemIndex := i; + i := comboDefaultRole.Items.IndexOf(User.DefaultRole); + if i > -1 then + comboDefaultRole.ItemIndex := i; + Caption := Caption + ' - ' + User.Username; AllPNames := TStringList.Create; @@ -551,7 +673,7 @@ procedure TUserManagerForm.listUsersFocusChanged(Sender: TBaseVirtualTree; Node: Grants.Add('GRANT USAGE ON *.* TO '+UserHost); end; end else try - Grants := FConnection.GetCol('SHOW GRANTS FOR '+FConnection.EscapeString(User.Username)+'@'+FConnection.EscapeString(User.Host)); + Grants := FConnection.GetCol('SHOW GRANTS FOR '+UserHost); except on E:EDbError do begin Msg := E.Message; @@ -676,68 +798,65 @@ procedure TUserManagerForm.listUsersFocusChanged(Sender: TBaseVirtualTree; Node: end; - // REQUIRE SSL X509 ISSUER '456' SUBJECT '789' CIPHER '123' NONE - rxTemp.Expression := '\sREQUIRE\s+(.+)'; - if rxTemp.Exec(rxGrant.Match[11]) then begin - RequireClause := rxTemp.Match[1]; - User.SSL := 0; - User.Cipher := ''; - User.Issuer := ''; - User.Subject := ''; - rxTemp.Expression := '\bSSL\b'; - if rxTemp.Exec(RequireClause) then - User.SSL := 1; - rxTemp.Expression := '\bX509\b'; - if rxTemp.Exec(RequireClause) then - User.SSL := 2; - rxTemp.Expression := '\bCIPHER\s+''([^'']+)'; - if rxTemp.Exec(RequireClause) then - User.Cipher := rxTemp.Match[1]; - rxTemp.Expression := '\bISSUER\s+''([^'']+)'; - if rxTemp.Exec(RequireClause) then - User.Issuer := rxTemp.Match[1]; - rxTemp.Expression := '\bSUBJECT\s+''([^'']+)'; - if rxTemp.Exec(RequireClause) then - User.Subject := rxTemp.Match[1]; - if IsNotEmpty(User.Cipher) or IsNotEmpty(User.Issuer) or IsNotEmpty(User.Subject) then - User.SSL := 3; - comboSSL.ItemIndex := User.SSL; - comboSSL.OnChange(Sender); - editCipher.Text := User.Cipher; - editIssuer.Text := User.Issuer; - editSubject.Text := User.Subject; - end; - - // WITH .. GRANT OPTION - // MAX_QUERIES_PER_HOUR 20 MAX_UPDATES_PER_HOUR 10 MAX_CONNECTIONS_PER_HOUR 5 MAX_USER_CONNECTIONS 2 - rxTemp.Expression := '\sWITH\s+(.+)'; - if rxTemp.Exec(rxGrant.Match[11]) then begin - WithClause := rxTemp.Match[1]; - if ExecRegExpr('\bGRANT\s+OPTION\b', WithClause) then - P.OrgPrivs.Add('GRANT'); - rxTemp.Expression := '\bMAX_QUERIES_PER_HOUR\s+(\d+)\b'; - if rxTemp.Exec(WithClause) then - User.MaxQueries := MakeInt(rxTemp.Match[1]); - rxTemp.Expression := '\bMAX_UPDATES_PER_HOUR\s+(\d+)\b'; - if rxTemp.Exec(WithClause) then - User.MaxUpdates := MakeInt(rxTemp.Match[1]); - rxTemp.Expression := '\bMAX_CONNECTIONS_PER_HOUR\s+(\d+)\b'; - if rxTemp.Exec(WithClause) then - User.MaxConnections := MakeInt(rxTemp.Match[1]); - rxTemp.Expression := '\bMAX_USER_CONNECTIONS\s+(\d+)\b'; - if rxTemp.Exec(WithClause) then - User.MaxUserConnections := MakeInt(rxTemp.Match[1]); - udMaxQueries.Position := User.MaxQueries; - udMaxUpdates.Position := User.MaxUpdates; - udMaxConnections.Position := User.MaxConnections; - udMaxUserConnections.Position := User.MaxUserConnections; - end; + User.ParseSettings(rxGrant.Match[11], P); if (P.OrgPrivs.Count = 0) and (P.DBObj.NodeType = lntTable) then FPrivObjects.Remove(P); end; end; + // Find roles assigned to user or role: + // GRANT role_admin TO 'root'@'127.0.0.1'; + // GRANT 'role space' TO 'root'@'127.0.0.1'; + // GRANT role_space to 'role_admin' WITH ADMIN OPTION; + DefaultRoles := FUsers.GetDefaultRoles(User^); + User.Roles.Assign(DefaultRoles); + RoleNames := FUsers.GetRoleNames; + for i:=0 to RoleNames.Count-1 do begin + RoleNames[i] := QuoteRegExprMetaChars(RoleNames[i]); + end; + rxGrant.Expression := '^GRANT\s+'+RxQuotes+'?('+Implode('|', RoleNames)+')'+RxQuotes+'?\s+'+ + 'TO\s+'+UserHostRx+'(\s+WITH ADMIN OPTION)?$'; + for i:=0 to Grants.Count-1 do begin + // Find selected priv objects via regular expression + if not rxGrant.Exec(Grants[i]) then begin + Continue; + end; + j := User.Roles.IndexOfName(rxGrant.Match[1]); + if j > -1 then begin + if rxGrant.MatchLen[2] > 0 then + User.Roles.ValueFromIndex[j] := User.RoleYesAdmin + else + User.Roles.ValueFromIndex[j] := User.RoleYes; + end; + end; + ValueListEditorRoles.BeginUpdate; + ValueListEditorRoles.Strings.Assign(User.Roles); + ValueListEditorRoles.EndUpdate; + DefaultRoles.Free; + + // Parse general user options + CreateUser := ''; + if User.IsUser then try + CreateUser := FConnection.GetVar('SHOW CREATE USER '+UserHost); + User.ParseSettings(CreateUser, nil); + except + on E:EDbError do; + end; + + udMaxQueries.Position := User.MaxQueries; + udMaxUpdates.Position := User.MaxUpdates; + udMaxConnections.Position := User.MaxConnections; + udMaxUserConnections.Position := User.MaxUserConnections; + comboSSL.ItemIndex := User.SSL; + comboSSL.OnChange(comboSSL); + editCipher.Text := User.Cipher; + editIssuer.Text := User.Issuer; + editSubject.Text := User.Subject; + tabPrivileges.Caption := _('Privileges') + ' (' + FPrivObjects.Count.ToString + ')'; + tabRoles.Caption := _('Roles') + ' (' + User.AssignedRolesCount.ToString + ')'; + + // Generate grant code for column privs by hand for Ptmp in FPrivObjects do begin if Ptmp.DBObj.NodeType = lntColumn then begin @@ -768,18 +887,22 @@ procedure TUserManagerForm.listUsersFocusChanged(Sender: TBaseVirtualTree; Node: treePrivs.Invalidate; // Enable input boxes - lblUsername.Enabled := UserSelected; - editUsername.Enabled := UserSelected; - lblFromHost.Enabled := UserSelected; - editFromHost.Enabled := UserSelected; - lblPassword.Enabled := UserSelected; - editPassword.Enabled := UserSelected; - lblRepeatPassword.Enabled := UserSelected; - editRepeatPassword.Enabled := UserSelected; + lblUsername.Enabled := UserSelected and User.IsUser; + editUsername.Enabled := UserSelected and User.IsUser; + lblFromHost.Enabled := UserSelected and User.IsUser; + editFromHost.Enabled := UserSelected and User.IsUser; + lblPassword.Enabled := UserSelected and User.IsUser; + editPassword.Enabled := UserSelected and User.IsUser; + lblRepeatPassword.Enabled := UserSelected and User.IsUser; + editRepeatPassword.Enabled := UserSelected and User.IsUser; + comboPlugins.Enabled := UserSelected and User.IsUser and FHasPlugin; + lblPlugin.Enabled := comboPlugins.Enabled; + comboDefaultRole.Enabled := UserSelected and User.IsUser and FHasDefaultRole; + lblDefaultRole.Enabled := comboDefaultRole.Enabled; tabCredentials.Enabled := UserSelected; - lblMaxQueries.Enabled := UserSelected and (FConnection.ServerVersionInt >= 40002); + lblMaxQueries.Enabled := UserSelected and User.IsUser and (FConnection.ServerVersionInt >= 40002); - tabLimitations.Enabled := UserSelected; + tabLimitations.Enabled := UserSelected and User.IsUser; editMaxQueries.Enabled := lblMaxQueries.Enabled; udMaxQueries.Enabled := lblMaxQueries.Enabled; lblMaxUpdates.Enabled := lblMaxQueries.Enabled; @@ -788,16 +911,16 @@ procedure TUserManagerForm.listUsersFocusChanged(Sender: TBaseVirtualTree; Node: lblMaxConnections.Enabled := lblMaxQueries.Enabled; editMaxConnections.Enabled := lblMaxQueries.Enabled; udMaxConnections.Enabled := lblMaxQueries.Enabled; - lblMaxUserConnections.Enabled := UserSelected and (FConnection.ServerVersionInt >= 50003); + lblMaxUserConnections.Enabled := UserSelected and User.IsUser and (FConnection.ServerVersionInt >= 50003); editMaxUserConnections.Enabled := lblMaxUserConnections.Enabled; udMaxUserConnections.Enabled := lblMaxUserConnections.Enabled; - tabSSL.Enabled := UserSelected; - comboSSL.Enabled := UserSelected; + tabSSL.Enabled := UserSelected and User.IsUser; + comboSSL.Enabled := UserSelected and User.IsUser; btnAddObject.Enabled := UserSelected; btnDeleteUser.Enabled := UserSelected; - btnCloneUser.Enabled := UserSelected and (not FAdded); + btnCloneUser.Enabled := UserSelected and (not FAdded) and User.IsUser; // Ensure the warning hint is displayed or cleared. This is not done when the dialog shows up. listUsers.OnHotChange(Sender, nil, Node); @@ -811,10 +934,15 @@ procedure TUserManagerForm.listUsersGetImageIndex(Sender: TBaseVirtualTree; Node begin if Column <> 0 then Exit; + User := Sender.GetNodeData(Node); case Kind of - ikNormal, ikSelected: ImageIndex := 43; + ikNormal, ikSelected: begin + if User.IsUser then + ImageIndex := ICONINDEX_USER + else + ImageIndex := ICONINDEX_ROLE; + end; ikOverlay: begin - User := Sender.GetNodeData(Node); if User.Password = '' then ImageIndex := 161; if FModified and (Node = Sender.FocusedNode) then @@ -842,6 +970,7 @@ procedure TUserManagerForm.listUsersGetText(Sender: TBaseVirtualTree; Node: PVir case Column of 0: CellText := User.Username; 1: CellText := User.Host; + 2: CellText := User.Plugin; end; end; @@ -1048,21 +1177,20 @@ procedure TUserManagerForm.treePrivsPaintText(Sender: TBaseVirtualTree; const Ta if (Sender.GetNodeLevel(Node) = 1) and (not (vsSelected in Node.States)) then begin PrivName := FPrivObjects[Node.Parent.Index].AllPrivileges[Node.Index]; if PrivsRead.IndexOf(PrivName) > -1 then - TargetCanvas.Font.Color := clGreen + TargetCanvas.Font.Color := FColorReadPriv else if PrivsWrite.IndexOf(PrivName) > -1 then - TargetCanvas.Font.Color := clMaroon + TargetCanvas.Font.Color := FColorWritePriv else if PrivsAdmin.IndexOf(PrivName) > -1 then - TargetCanvas.Font.Color := clNavy; + TargetCanvas.Font.Color := FColorAdminPriv; end; end; -procedure TUserManagerForm.btnAddUserClick(Sender: TObject); +procedure TUserManagerForm.menuItemUserClick(Sender: TObject); var P: TPrivObj; User: TUser; - OldUser, NodeUser: PUser; - Node: PVirtualNode; + OldUser: PUser; NewHost, NewPassword, NewUsername: String; begin // Create new or clone existing user @@ -1090,20 +1218,63 @@ procedure TUserManagerForm.btnAddUserClick(Sender: TObject); FUsers.Add(User); FAdded := True; InvalidateVT(listUsers, VTREE_NOTLOADED, True); - // Select newly added item. + SelectUserNode(User); + Modified := True; + // Focus the user name entry box. + PageControlSettings.ActivePage := tabCredentials; + editUserName.SetFocus; +end; + + +procedure TUserManagerForm.menuItemRoleClick(Sender: TObject); +var + RoleName, CreateRole: String; + User: TUser; +begin + // Try to unfocus current user which triggers saving modifications. + listUsers.FocusedNode := nil; + if Assigned(listUsers.FocusedNode) then + Exit; + + // Add role + RoleName := ''; + if not InputQuery(_('Create role'), _('Role name'), RoleName) then + Exit; + + try + CreateRole := FConnection.SqlProvider.GetSql(qCreateRole, [FConnection.EscapeString(RoleName)]); + FConnection.Query(CreateRole); + User := TUser.Create; + User.Username := RoleName; + User.IsRole := True; + FUsers.Add(User); + InvalidateVT(listUsers, VTREE_NOTLOADED, True); + SelectUserNode(User); + Modified := True; + except + on E:Exception do + ErrorDialog(E.Message); + end; +end; + + +function TUserManagerForm.SelectUserNode(User: TUser): Boolean; +var + NodeUser: PUser; + Node: PVirtualNode; +begin + // Select a user node in the listing + Result := False; Node := listUsers.GetFirst; while Assigned(Node) do begin NodeUser := listUsers.GetNodeData(Node); if User = NodeUser^ then begin SelectNode(listUsers, Node); + Result := True; break; end; Node := listUsers.GetNextSibling(Node); end; - Modified := True; - // Focus the user name entry box. - PageControlSettings.ActivePage := tabCredentials; - editUserName.SetFocus; end; @@ -1172,8 +1343,9 @@ procedure TUserManagerForm.btnSaveClick(Sender: TObject); FocusedUser: PUser; Tables, WithClauses: TStringList; P: TPrivObj; - i: Integer; - PasswordSet: Boolean; + i, j: Integer; + PasswordSet, WithGrant: Boolean; + RoleName, RoleAssigned: String; function GetObjectType(ObjType: String): String; begin @@ -1186,10 +1358,11 @@ procedure TUserManagerForm.btnSaveClick(Sender: TObject); begin // Save changes FocusedUser := listUsers.GetNodeData(listUsers.FocusedNode); + FocusedUser.Problem := upNone; if FAdded then begin FocusedUser.Username := editUsername.Text; FocusedUser.Host := editFromHost.Text; - if IsEmpty(editPassword.Text) then + if IsEmpty(editPassword.Text) and FocusedUser.IsUser then FocusedUser.Problem := upEmptyPassword; end else begin if (FocusedUser.Problem=upNone) @@ -1199,8 +1372,14 @@ procedure TUserManagerForm.btnSaveClick(Sender: TObject); FocusedUser.Problem := upEmptyPassword end; - OrgUserHost := FConnection.EscapeString(FocusedUser.Username)+'@'+FConnection.EscapeString(FocusedUser.Host); - UserHost := FConnection.EscapeString(editUsername.Text)+'@'+FConnection.EscapeString(editFromHost.Text); + if FocusedUser.IsUser then begin + OrgUserHost := FConnection.EscapeString(FocusedUser.Username)+'@'+FConnection.EscapeString(FocusedUser.Host); + UserHost := FConnection.EscapeString(editUsername.Text)+'@'+FConnection.EscapeString(editFromHost.Text); + end + else begin + OrgUserHost := FConnection.EscapeString(FocusedUser.Username); + UserHost := FConnection.EscapeString(editUsername.Text); + end; try // Ensure we have a unique user@host combination @@ -1208,7 +1387,7 @@ procedure TUserManagerForm.btnSaveClick(Sender: TObject); if User = FocusedUser^ then Continue; if (User.Username = editUsername.Text) and (User.Host = editFromHost.Text) then - raise EInputError.CreateFmt('User <%s@%s> already exists.', [editUsername.Text, editFromHost.Text]); + raise EInputError.CreateFmt('User or role <%s@%s> already exists.', [editUsername.Text, editFromHost.Text]); end; // Check input: Ensure we have a unique user@host combination @@ -1218,15 +1397,21 @@ procedure TUserManagerForm.btnSaveClick(Sender: TObject); // Create added user PasswordSet := False; if FAdded and (FConnection.ServerVersionInt >= 50002) then begin - Create := 'CREATE USER '+UserHost; + Create := 'CREATE USER '+UserHost+' '; if editPassword.Modified then begin + // Insert authentication plugin with minor MariaDB/MySQL difference + if comboPlugins.ItemIndex > 0 then + Create := Create + 'IDENTIFIED ' + FSQLPluginPrefix + ' ' + comboPlugins.Text+' ' + FSQLPluginPassPrefix + ' ' + else + Create := Create + 'IDENTIFIED BY '; // Add "PASSWORD" clause when it's a hash already if (Copy(editPassword.Text, 1, 1) = '*') and (Length(editPassword.Text) = 41) then - Create := Create + ' IDENTIFIED BY PASSWORD '+FConnection.EscapeString(editPassword.Text) + Create := Create + 'PASSWORD '+FConnection.EscapeString(editPassword.Text) else - Create := Create + ' IDENTIFIED BY '+FConnection.EscapeString(editPassword.Text); + Create := Create + FConnection.EscapeString(editPassword.Text); end; FConnection.Query(Create); + FConnection.ShowWarnings; PasswordSet := True; end; @@ -1262,6 +1447,7 @@ procedure TUserManagerForm.btnSaveClick(Sender: TObject); Delete(Revoke, Length(Revoke)-1, 1); Revoke := 'REVOKE ' + Revoke + ' ON ' + OnObj + ' FROM ' + OrgUserHost; FConnection.Query(Revoke); + FConnection.ShowWarnings; end; // Grant privileges. Must be applied with USAGE for added users without specific privs. @@ -1279,45 +1465,44 @@ procedure TUserManagerForm.btnSaveClick(Sender: TObject); Grant := 'USAGE'; Grant := 'GRANT ' + Grant + ' ON ' + OnObj + ' TO ' + OrgUserHost; - // SSL options - if P.DBObj.NodeType = lntNone then begin - RequireClause := ' REQUIRE '; - case comboSSL.ItemIndex of - 0: RequireClause := RequireClause + 'NONE'; - 1: RequireClause := RequireClause + 'SSL'; - 2: RequireClause := RequireClause + 'X509'; - 3: RequireClause := RequireClause + 'CIPHER '+FConnection.EscapeString(editCipher.Text)+' ISSUER '+FConnection.EscapeString(editIssuer.Text)+' SUBJECT '+FConnection.EscapeString(editSubject.Text); - end; - if (FocusedUser.SSL = comboSSL.ItemIndex) - and (FocusedUser.Cipher = editCipher.Text) - and (FocusedUser.Issuer = editIssuer.Text) - and (FocusedUser.Subject = editSubject.Text) - then - RequireClause := ''; - Grant := Grant + RequireClause; - end; + WithGrant := P.AddedPrivs.IndexOf('GRANT') > -1; + if WithGrant then + Grant := Grant + ' WITH GRANT OPTION'; - WithClauses := TStringList.Create; - if P.AddedPrivs.IndexOf('GRANT') > -1 then - WithClauses.Add('GRANT OPTION'); - if P.DBObj.NodeType = lntNone then begin - // Apply resource limits only to global privilege - if udMaxQueries.Position <> FocusedUser.MaxQueries then - WithClauses.Add('MAX_QUERIES_PER_HOUR '+IntToStr(udMaxQueries.Position)); - if udMaxUpdates.Position <> FocusedUser.MaxUpdates then - WithClauses.Add('MAX_UPDATES_PER_HOUR '+IntToStr(udMaxUpdates.Position)); - if udMaxConnections.Position <> FocusedUser.MaxConnections then - WithClauses.Add('MAX_CONNECTIONS_PER_HOUR '+IntToStr(udMaxConnections.Position)); - if udMaxUserConnections.Position <> FocusedUser.MaxUserConnections then - WithClauses.Add('MAX_USER_CONNECTIONS '+IntToStr(udMaxUserConnections.Position)); + if P.Added or (P.AddedPrivs.Count > 0) or WithGrant then begin + FConnection.Query(Grant); + FConnection.ShowWarnings; end; - if WithClauses.Count > 0 then - Grant := Grant + ' WITH ' + Implode(' ', WithClauses); - if P.Added or (P.AddedPrivs.Count > 0) or (WithClauses.Count > 0) or (RequireClause <> '') then - FConnection.Query(Grant); + // General user options + if (P.DBObj.NodeType = lntNone) and FocusedUser.IsUser then begin + // Plugin + if comboPlugins.ItemIndex > 0 then begin + FConnection.Query('ALTER USER ' + UserHost + ' IDENTIFIED ' + FSQLPluginPrefix + ' ' + comboPlugins.Text); + FConnection.ShowWarnings; + end; + + // SSL + case comboSSL.ItemIndex of + 1: RequireClause := 'SSL'; + 2: RequireClause := 'X509'; + 3: RequireClause := 'CIPHER '+FConnection.EscapeString(editCipher.Text)+' AND ISSUER '+FConnection.EscapeString(editIssuer.Text)+' AND SUBJECT '+FConnection.EscapeString(editSubject.Text); + else RequireClause := 'NONE'; + end; + FConnection.Query('ALTER USER ' + UserHost + ' REQUIRE ' + RequireClause); + FConnection.ShowWarnings; + + // Resource limits, with 0 by default + WithClauses := TStringList.Create; + WithClauses.Add('MAX_QUERIES_PER_HOUR '+IntToStr(udMaxQueries.Position)); + WithClauses.Add('MAX_UPDATES_PER_HOUR '+IntToStr(udMaxUpdates.Position)); + WithClauses.Add('MAX_CONNECTIONS_PER_HOUR '+IntToStr(udMaxConnections.Position)); + WithClauses.Add('MAX_USER_CONNECTIONS '+IntToStr(udMaxUserConnections.Position)); + FConnection.Query('ALTER USER ' + UserHost + ' WITH ' + Implode(' ', WithClauses)); + FConnection.ShowWarnings; + WithClauses.Free; + end; - WithClauses.Free; end; // Set password @@ -1326,35 +1511,81 @@ procedure TUserManagerForm.btnSaveClick(Sender: TObject); FConnection.Query('SET PASSWORD FOR ' + OrgUserHost + ' = '+FConnection.EscapeString(editPassword.Text)) else FConnection.Query('SET PASSWORD FOR ' + OrgUserHost + ' = PASSWORD('+FConnection.EscapeString(editPassword.Text)+')'); + FConnection.ShowWarnings; end; + // Add or remove roles + for i:=0 to ValueListEditorRoles.Strings.Count-1 do begin + j := FocusedUser.Roles.IndexOf(ValueListEditorRoles.Strings[i]); + if j = -1 then begin + RoleName := ValueListEditorRoles.Strings.Names[i]; + RoleAssigned := ValueListEditorRoles.Strings.ValueFromIndex[i]; + if RoleAssigned = TUser.RoleNo then + FConnection.Query(qRevokeRole, [FConnection.EscapeString(RoleName), OrgUserHost]) + else if RoleAssigned = TUser.RoleYes then + FConnection.Query(qGrantRole, [FConnection.EscapeString(RoleName), OrgUserHost, '']) + else if RoleAssigned = TUser.RoleYesAdmin then + FConnection.Query(qGrantRole, [FConnection.EscapeString(RoleName), OrgUserHost, ' WITH ADMIN OPTION']); + FConnection.ShowWarnings; + end; + end; + + // Set default role + if comboDefaultRole.Enabled and (comboDefaultRole.ItemIndex > -1) then begin + if comboDefaultRole.ItemIndex = 0 then begin + FConnection.Query(qSetDefaultRole, ['NONE', OrgUserHost]); + end + else try + RoleName := comboDefaultRole.Text; + RoleAssigned := ValueListEditorRoles.Strings.Values[RoleName]; + if (RoleAssigned = TUser.RoleYes) or (RoleAssigned = TUser.RoleYesAdmin) then + FConnection.Query(qSetDefaultRole, [FConnection.EscapeString(RoleName), OrgUserHost]); + except + on E:EDbError do; // Happens when this role was not granted before + end; + FConnection.ShowWarnings; + end; + + // Rename user if (FocusedUser.Username <> editUsername.Text) or (FocusedUser.Host <> editFromHost.Text) then begin - if FConnection.ServerVersionInt >= 50002 then - FConnection.Query('RENAME USER '+OrgUserHost+' TO '+UserHost) - else begin - Tables := Explode(',', 'user,db,tables_priv,columns_priv'); - for Table in Tables do begin - FConnection.Query('UPDATE '+FConnection.QuoteIdent('mysql')+'.'+FConnection.QuoteIdent(Table)+ - ' SET User='+FConnection.EscapeString(editUsername.Text)+', Host='+FConnection.EscapeString(editFromHost.Text)+ - ' WHERE User='+FConnection.EscapeString(FocusedUser.Username)+' AND Host='+FConnection.EscapeString(FocusedUser.Host) - ); + + if FocusedUser.IsUser then begin + if FConnection.ServerVersionInt >= 50002 then + FConnection.Query('RENAME USER '+OrgUserHost+' TO '+UserHost) + else begin + Tables := Explode(',', 'user,db,tables_priv,columns_priv'); + for Table in Tables do begin + FConnection.Query('UPDATE '+FConnection.QuoteIdent('mysql')+'.'+FConnection.QuoteIdent(Table)+ + ' SET User='+FConnection.EscapeString(editUsername.Text)+', Host='+FConnection.EscapeString(editFromHost.Text)+ + ' WHERE User='+FConnection.EscapeString(FocusedUser.Username)+' AND Host='+FConnection.EscapeString(FocusedUser.Host) + ); + end; + FreeAndNil(Tables); end; - FreeAndNil(Tables); + end + + else begin + // todo: Rename role end; + + FConnection.ShowWarnings; end; - FConnection.Query('FLUSH PRIVILEGES'); + FConnection.Query(qReloadPrivileges); Modified := False; FAdded := False; FocusedUser.Username := editUsername.Text; FocusedUser.Host := editFromHost.Text; if editPassword.Modified then FocusedUser.Password := editPassword.Text; + FocusedUser.Plugin := IfThen(comboPlugins.ItemIndex=0, '', comboPlugins.Text); + FocusedUser.DefaultRole := IfThen(comboDefaultRole.ItemIndex=0, '', comboDefaultRole.Text); FocusedUser.SSL := comboSSL.ItemIndex; FocusedUser.Cipher := editCipher.Text; FocusedUser.Issuer := editIssuer.Text; FocusedUser.Subject := editSubject.Text; + FocusedUser.Roles.Assign(ValueListEditorRoles.Strings); listUsers.OnFocusChanged(listUsers, listUsers.FocusedNode, listUsers.FocusedColumn); except on E:EDbError do @@ -1390,24 +1621,40 @@ procedure TUserManagerForm.btnDeleteUserClick(Sender: TObject); FUsers.Remove(User^); listUsers.DeleteNode(listUsers.FocusedNode); FAdded := False; - end else if MessageDialog(f_('Delete user %s@%s?', [User.Username, User.Host]), mtConfirmation, [mbYes, mbCancel]) = mrYes then begin - UserHost := FConnection.EscapeString(User.Username)+'@'+FConnection.EscapeString(User.Host); - try - // Revoke privs explicitly, required on old servers. - // Newer servers only require one DROP USER query - if FConnection.ServerVersionInt < 50002 then begin - FConnection.Query('REVOKE ALL PRIVILEGES ON *.* FROM '+UserHost); - FConnection.Query('REVOKE GRANT OPTION ON *.* FROM '+UserHost); + end else begin + UserHost := IfThen( + User.IsUser, + FConnection.EscapeString(User.Username)+'@'+FConnection.EscapeString(User.Host), + FConnection.EscapeString(User.Username) + ); + if MessageDialog(f_('Delete user or role %s?', [UserHost]), mtConfirmation, [mbYes, mbCancel]) = mrYes then begin + try + // Revoke privs explicitly, required on old servers. + // Newer servers only require one DROP USER query + if FConnection.ServerVersionInt < 50002 then begin + FConnection.Query('REVOKE ALL PRIVILEGES ON *.* FROM '+UserHost); + FConnection.Query('REVOKE GRANT OPTION ON *.* FROM '+UserHost); + end; + + // Actual deletion + if User.IsUser then begin + FConnection.Query(qDropUser, [ + FConnection.EscapeString(User.Username), + FConnection.EscapeString(User.Host) + ]); + end + else begin + FConnection.Query(qDropRole, [ + FConnection.EscapeString(User.Username) + ]); + end; + FConnection.Query(qReloadPrivileges); + + FUsers.Remove(User^); + listUsers.DeleteNode(listUsers.FocusedNode); + except on E:EDbError do + ErrorDialog(E.Message); end; - if FConnection.ServerVersionInt < 40101 then - FConnection.Query('DELETE FROM mysql.user WHERE User='+FConnection.EscapeString(User.Username)+' AND Host='+FConnection.EscapeString(User.Host)) - else - FConnection.Query('DROP USER '+UserHost); - FConnection.Query('FLUSH PRIVILEGES'); - FUsers.Remove(User^); - listUsers.DeleteNode(listUsers.FocusedNode); - except on E:EDbError do - ErrorDialog(E.Message); end; end; end; @@ -1421,6 +1668,11 @@ procedure TUserManagerForm.btnDiscardClick(Sender: TObject); end; +procedure TUserManagerForm.menuAddPopup(Sender: TObject); +begin + menuItemRole.Enabled := FHasIsRole; +end; + procedure TUserManagerForm.menuHostClick(Sender: TObject); begin // Insert predefined host @@ -1465,6 +1717,17 @@ procedure TUserManagerForm.menuHostPopup(Sender: TObject); end; +procedure TUserManagerForm.editFilterUsersChange(Sender: TObject); +begin + // Filter nodes in query helpers + FilterNodesByEdit(Sender as TButtonedEdit, listUsers); +end; + +procedure TUserManagerForm.editFilterUsersRightButtonClick(Sender: TObject); +begin + MainForm.buttonedEditClear(Sender); +end; + procedure TUserManagerForm.editPasswordChange(Sender: TObject); begin // Password manually edited @@ -1511,10 +1774,40 @@ procedure TUserManagerForm.menuPasswordClick(Sender: TObject); end; +{ TUser } + +constructor TUser.Create; +begin + Username := ''; + Host := ''; + Password := ''; + DefaultRole := ''; + Cipher := ''; + Issuer := ''; + Subject := ''; + MaxQueries := 0; + MaxUpdates := 0; + MaxConnections := 0; + MaxUserConnections := 0; + SSL := 0; + Problem := upNone; + IsRole := False; + Roles := TStringList.Create; +end; + +destructor TUser.Destroy; +begin + Roles.Free; + inherited; +end; + function TUser.HostRequiresNameResolve: Boolean; var rx: TRegExpr; begin + Result := False; + if IsRole then + Exit; rx := TRegExpr.Create; // Valid ips or wildcards which do not need name resolving: rx.Expression := '^(localhost|[\d\.\/\:_]+|.*%.*|[\w\d_]{4}\:.*)$'; @@ -1522,8 +1815,122 @@ function TUser.HostRequiresNameResolve: Boolean; rx.Free; end; +procedure TUser.ParseSettings(GrantOrCreate: String; Priv: TPrivObj); +var + rx: TRegExpr; + RequireClause, WithClause: String; +begin + // CREATE USER ... + // mysql: IDENTIFIED WITH 'mysql_native_password' AS '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257' REQUIRE SSL X509 ISSUER '456' SUBJECT '789' CIPHER '123' NONE + // mariadb: IDENTIFIED BY PASSWORD '23AE809DDACAF96A'; + rx := TRegExpr.Create; + rx.ModifierI := True; + rx.Expression := '\sREQUIRE\s+(.+)'; + if rx.Exec(GrantOrCreate) then begin + RequireClause := rx.Match[1]; + SSL := 0; + Cipher := ''; + Issuer := ''; + Subject := ''; + rx.Expression := '\bSSL\b'; + if rx.Exec(RequireClause) then + SSL := 1; + rx.Expression := '\bX509\b'; + if rx.Exec(RequireClause) then + SSL := 2; + rx.Expression := '\bCIPHER\s+''([^'']+)'; + if rx.Exec(RequireClause) then + Cipher := rx.Match[1]; + rx.Expression := '\bISSUER\s+''([^'']+)'; + if rx.Exec(RequireClause) then + Issuer := rx.Match[1]; + rx.Expression := '\bSUBJECT\s+''([^'']+)'; + if rx.Exec(RequireClause) then + Subject := rx.Match[1]; + if IsNotEmpty(Cipher) or IsNotEmpty(Issuer) or IsNotEmpty(Subject) then + SSL := 3; + end; + // WITH .. GRANT OPTION + // MAX_QUERIES_PER_HOUR 20 MAX_UPDATES_PER_HOUR 10 MAX_CONNECTIONS_PER_HOUR 5 MAX_USER_CONNECTIONS 2 + rx.Expression := '\sWITH\s+(.+)'; + if rx.Exec(GrantOrCreate) then begin + WithClause := rx.Match[1]; + if ExecRegExpr('\bGRANT\s+OPTION\b', WithClause) and Assigned(Priv) then + Priv.OrgPrivs.Add('GRANT'); + rx.Expression := '\bMAX_QUERIES_PER_HOUR\s+(\d+)\b'; + if rx.Exec(WithClause) then + MaxQueries := MakeInt(rx.Match[1]); + rx.Expression := '\bMAX_UPDATES_PER_HOUR\s+(\d+)\b'; + if rx.Exec(WithClause) then + MaxUpdates := MakeInt(rx.Match[1]); + rx.Expression := '\bMAX_CONNECTIONS_PER_HOUR\s+(\d+)\b'; + if rx.Exec(WithClause) then + MaxConnections := MakeInt(rx.Match[1]); + rx.Expression := '\bMAX_USER_CONNECTIONS\s+(\d+)\b'; + if rx.Exec(WithClause) then + MaxUserConnections := MakeInt(rx.Match[1]); + end; +end; + + +function TUser.IsUser: Boolean; +begin + Result := not IsRole; +end; + + +function TUser.AssignedRolesCount: Integer; +var + i: Integer; + Val: String; +begin + Result := 0; + for i:=0 to Roles.Count-1 do begin + Val := Roles.ValueFromIndex[i]; + if (Val = RoleYes) or (Val = RoleYesAdmin) then + Inc(Result); + end; +end; +{ TUserList } + +function TUserList.GetRoleNames: TStringList; +var + u: TUser; +begin + Result := TStringList.Create; + for u in Self do begin + if u.IsRole then + Result.Add(u.Username); + end; +end; + +procedure TUserList.GetRoleNames(Strings: TStrings); +var + RoleNames: TStringList; +begin + RoleNames := GetRoleNames; + Strings.AddStrings(RoleNames); + RoleNames.Free; +end; + +function TUserList.GetDefaultRoles(ExcludeRole: TUser): TStringList; +var + RoleNames: TStringList; + i: Integer; +begin + // Default role assignments with "no" value for all roles + Result := TStringList.Create; + RoleNames := GetRoleNames; + for i:=0 to RoleNames.Count-1 do begin + if ExcludeRole.IsRole and SameText(RoleNames[i], ExcludeRole.Username) then + Continue; + Result.AddPair(RoleNames[i], TUser.RoleNo); + end; + RoleNames.Free; +end; + { TPrivObj } @@ -1566,4 +1973,10 @@ function TPrivComparer.Compare(const Left, Right: TPrivObj): Integer; end; +initialization + +TUser.RoleNo := _('No'); +TUser.RoleYes := _('Yes'); +TUser.RoleYesAdmin := _('Yes, with admin option'); + end. diff --git a/source/vcl-styles-utils/Vcl.Styles.Ext.pas b/source/vcl-styles-utils/Vcl.Styles.Ext.pas index da97dea62..bfdbf792e 100644 --- a/source/vcl-styles-utils/Vcl.Styles.Ext.pas +++ b/source/vcl-styles-utils/Vcl.Styles.Ext.pas @@ -257,12 +257,17 @@ implementation Vcl.Direct2D, System.StrUtils, Winapi.D2D1, -{$IFEND} +{$IFEND} +{$IF CompilerVersion >= 36} + Vcl.StyleBitmap, + Vcl.StyleAPI, +{$IFEND} Winapi.Messages, {$ENDIF} - Vcl.Dialogs, Vcl.Styles.Utils.Misc, Vcl.Styles.Utils.Graphics; + Vcl.Dialogs, Vcl.Styles.Utils.Misc, + Vcl.Styles.Utils.Graphics; -{$IF (DEFINED (USE_VCL_STYLESAPI) AND (CompilerVersion >= 23))} +{$IF (DEFINED (USE_VCL_STYLESAPI) AND (CompilerVersion >= 23) AND (CompilerVersion <= 35))} {$I '..\source\vcl\StyleUtils.inc'} {$I '..\source\vcl\StyleAPI.inc'} {$IFEND} @@ -589,6 +594,20 @@ procedure RemoveEmptyVCLStyleHook(ControlClass: TClass); end; {$IFDEF USE_VCL_STYLESAPI} +type + TseStyleHelper = class Helper for TseStyle + strict private + function GetCleanCopy: TSeStyleSource; + public + property CleanCopy: TSeStyleSource read GetCleanCopy; + end; + +function TseStyleHelper.GetCleanCopy: TSeStyleSource; +begin + with Self do + Result := FCleanCopy; +end; + { TVCLStyleExt } constructor TCustomStyleExt.Create(const FileName: string); @@ -610,22 +629,22 @@ procedure TCustomStyleExt.CopyToStream(Stream: TStream); Stream.Size := 0; Stream.Position := 0; - TseStyle(Source).FCleanCopy.Name := TseStyle(Source).StyleSource.Name; - TseStyle(Source).FCleanCopy.Author := TseStyle(Source).StyleSource.Author; - TseStyle(Source).FCleanCopy.AuthorEMail := TseStyle(Source).StyleSource.AuthorEMail; - TseStyle(Source).FCleanCopy.AuthorURL := TseStyle(Source).StyleSource.AuthorURL; - TseStyle(Source).FCleanCopy.Version := TseStyle(Source).StyleSource.Version; + TseStyle(Source).CleanCopy.Name := TseStyle(Source).StyleSource.Name; + TseStyle(Source).CleanCopy.Author := TseStyle(Source).StyleSource.Author; + TseStyle(Source).CleanCopy.AuthorEMail := TseStyle(Source).StyleSource.AuthorEMail; + TseStyle(Source).CleanCopy.AuthorURL := TseStyle(Source).StyleSource.AuthorURL; + TseStyle(Source).CleanCopy.Version := TseStyle(Source).StyleSource.Version; // Replace the modified bitmaps - for I := 0 to TseStyle(Source).FCleanCopy.Bitmaps.Count - 1 do - TseStyle(Source).FCleanCopy.Bitmaps[I].Assign(TseStyle(Source).StyleSource.Bitmaps[I]); + for I := 0 to TseStyle(Source).CleanCopy.Bitmaps.Count - 1 do + TseStyle(Source).CleanCopy.Bitmaps[I].Assign(TseStyle(Source).StyleSource.Bitmaps[I]); // TseStyle(Source).StyleSource.SysColors.Assign(TseStyle(Source).SysColors); // Replace the modified colors - TseStyle(Source).FCleanCopy.SysColors.Assign(TseStyle(Source).SysColors); - TseStyle(Source).FCleanCopy.Colors.Assign(TseStyle(Source).Colors); - TseStyle(Source).FCleanCopy.Fonts.Assign(TseStyle(Source).Fonts); + TseStyle(Source).CleanCopy.SysColors.Assign(TseStyle(Source).SysColors); + TseStyle(Source).CleanCopy.Colors.Assign(TseStyle(Source).Colors); + TseStyle(Source).CleanCopy.Fonts.Assign(TseStyle(Source).Fonts); // ShowMessage(ColorToString(TseStyle(Source).SysColors[clWindow])); TseStyle(Source).SaveToStream(Stream); @@ -1867,13 +1886,17 @@ procedure TVclStylesPreview.Paint; initialization {$IFDEF USE_VCL_STYLESAPI} + {$IF CompilerVersion <= 35} InitStyleAPI; + {$IFEND} {$ENDIF} finalization {$IFDEF USE_VCL_STYLESAPI} + {$IF CompilerVersion <= 35} FinalizeStyleAPI; + {$IFEND} {$ENDIF} end. diff --git a/source/vcl-styles-utils/Vcl.Styles.FormStyleHooks.pas b/source/vcl-styles-utils/Vcl.Styles.FormStyleHooks.pas index 2546478a5..8269395c7 100644 --- a/source/vcl-styles-utils/Vcl.Styles.FormStyleHooks.pas +++ b/source/vcl-styles-utils/Vcl.Styles.FormStyleHooks.pas @@ -147,7 +147,7 @@ TFormStyleHookHelper = class helper for TFormStyleHook property _FSysMenuButtonRect: TRect read GetFSysMenuButtonRect Write SetFSysMenuButtonRect; property _FCaptionRect: TRect read GetFCaptionRect Write SetFCaptionRect; - function _GetBorderSize: TRect; + function _GetBorderSize{$IF CompilerVersion >= 36}(UseActiveStyle: Boolean = True){$IFEND}: TRect; property _FFormActive: Boolean read GetFFormActive; property _FChangeSizeCalled: Boolean read GetFChangeSizeCalled write SetFChangeSizeCalled; @@ -1433,15 +1433,15 @@ procedure TFormStyleHookHelper._ChangeSize; ChangeSize; end; -function TFormStyleHookHelper._GetBorderSize: TRect; +function TFormStyleHookHelper._GetBorderSize{$IF CompilerVersion >= 36}(UseActiveStyle: Boolean = True){$IFEND}: TRect; begin with Self do - Result := GetBorderSize; + Result := GetBorderSize; end; function TFormStyleHookHelper._GetBorderSizeAddr: Pointer; var - MethodAddr: function: TRect of object; + MethodAddr: function{$IF CompilerVersion >= 36}(UseActiveStyle: Boolean = True){$IFEND}: TRect of object; begin with Self do MethodAddr := GetBorderSize; diff --git a/source/vcl-styles-utils/Vcl.Styles.Hooks.pas b/source/vcl-styles-utils/Vcl.Styles.Hooks.pas index 12cdc2ad2..43fbf00aa 100644 --- a/source/vcl-styles-utils/Vcl.Styles.Hooks.pas +++ b/source/vcl-styles-utils/Vcl.Styles.Hooks.pas @@ -35,53 +35,39 @@ implementation {$I VCL.Styles.Utils.inc} uses - DDetours, - System.SyncObjs, - System.SysUtils, - System.Types, - System.UITypes, - System.Classes, - System.Generics.Collections, - System.StrUtils, - WinApi.Messages, - WinApi.UXTheme, - Vcl.Graphics, - Vcl.Styles.Utils.Graphics, + DDetours, System.SyncObjs, System.SysUtils, System.Types, System.UITypes, + System.Classes, System.Generics.Collections, System.StrUtils, WinApi.Messages, + WinApi.UXTheme, Vcl.Graphics, Vcl.Styles.Utils.Graphics, {$IFDEF HOOK_UXTHEME} Vcl.Styles.UxTheme, {$ENDIF HOOK_UXTHEME} - Vcl.Styles.Utils.SysControls, - Vcl.Styles.FontAwesome, - Vcl.Forms, - Vcl.Controls, - Vcl.StdCtrls, - Vcl.ComCtrls, - Vcl.Themes, - Vcl.Styles.Utils.Misc; + Vcl.Styles.Utils.SysControls, Vcl.Styles.FontAwesome, Vcl.Forms, Vcl.Controls, + Vcl.StdCtrls, Vcl.ComCtrls, Vcl.Themes, Vcl.Styles.Utils.Misc; type - TListStyleBrush = class(TDictionary) + TListStyleBrush = class(TDictionary) protected procedure ValueNotify(const Value: HBRUSH; Action: TCollectionNotification); override; end; TSetStyle = procedure(Style: TCustomStyleServices) of object; + TMonthCalendarClass = class(TMonthCalendar); + TCommonCalendarClass = class(TCommonCalendar); var VCLStylesBrush: TObjectDictionary; VCLStylesLock: TCriticalSection = nil; LSetStylePtr: TSetStyle; - Trampoline_SetStyle: procedure(Self: TObject; Style: TCustomStyleServices); Trampoline_user32_FillRect: function(hDC: hDC; const lprc: TRect; hbr: HBRUSH): Integer; stdcall; - Trampoline_user32_DrawEdge: function(hDC: hDC; var qrc: TRect; edge: UINT; grfFlags: UINT): BOOL; stdcall = nil; - Trampoline_user32_DrawFrameControl: function (DC: HDC; Rect: PRect; uType, uState: UINT): BOOL; stdcall = nil; - Trampoline_user32_LoadIconW: function (hInstance: HINST; lpIconName: PWideChar): HICON; stdcall = nil; + Trampoline_user32_DrawEdge: function(hDC: hDC; var qrc: TRect; edge: UINT; grfFlags: UINT): BOOL; stdcall = nil; + Trampoline_user32_DrawFrameControl: function(DC: HDC; Rect: PRect; uType, uState: UINT): BOOL; stdcall = nil; + Trampoline_user32_LoadIconW: function(hInstance: HINST; lpIconName: PWideChar): HICON; stdcall = nil; Trampoline_user32_GetSysColorBrush: function(nIndex: Integer): HBRUSH; stdcall; {$IFDEF HOOK_UXTHEME} - Trampoline_user32_LoadImageW: function (hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, Y: Integer; Flags: UINT): THandle; stdcall = nil; + Trampoline_user32_LoadImageW: function(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, Y: Integer; Flags: UINT): THandle; stdcall = nil; {$ELSE} Trampoline_user32_GetSysColor: function(nIndex: Integer): DWORD; stdcall; {$ENDIF HOOK_UXTHEME} @@ -92,13 +78,12 @@ TCommonCalendarClass = class(TCommonCalendar); {$IFEND CompilerVersion} {$ENDIF HOOK_TDateTimePicker} - function Detour_DrawEdge(hDC: hDC; var qrc: TRect; edge: UINT; grfFlags: UINT): BOOL; stdcall; var CanDraw: Boolean; SaveIndex: Integer; begin - if not(ExecutingInMainThread) then + if not (ExecutingInMainThread) then Exit(Trampoline_user32_DrawEdge(hDC, qrc, edge, grfFlags)); CanDraw := (not StyleServices.IsSystemStyle) and (TSysStyleManager.Enabled); @@ -117,7 +102,7 @@ function Detour_DrawEdge(hDC: hDC; var qrc: TRect; edge: UINT; grfFlags: UINT): function Detour_FillRect(hDC: hDC; const lprc: TRect; hbr: HBRUSH): Integer; stdcall; begin - if not(ExecutingInMainThread) or StyleServices.IsSystemStyle or not(TSysStyleManager.Enabled) then + if not (ExecutingInMainThread) or StyleServices.IsSystemStyle or not (TSysStyleManager.Enabled) then Exit(Trampoline_user32_FillRect(hDC, lprc, hbr)) else if (hbr > 0) and (hbr < COLOR_ENDCOLORS + 1) then Exit(Trampoline_user32_FillRect(hDC, lprc, GetSysColorBrush(hbr - 1))) @@ -127,7 +112,7 @@ function Detour_FillRect(hDC: hDC; const lprc: TRect; hbr: HBRUSH): Integer; std function Detour_GetSysColor(nIndex: Integer): DWORD; stdcall; begin - if not(ExecutingInMainThread) or StyleServices.IsSystemStyle or not(TSysStyleManager.Enabled) then + if not (ExecutingInMainThread) or StyleServices.IsSystemStyle or not (TSysStyleManager.Enabled) then Result := Trampoline_user32_GetSysColor(nIndex) else if nIndex = COLOR_HOTLIGHT then Result := DWORD(StyleServices.GetSystemColor(clHighlight)) @@ -141,7 +126,7 @@ function Detour_GetSysColorBrush(nIndex: Integer): HBRUSH; stdcall; LBrush: HBRUSH; LColor: TColor; begin - if not(ExecutingInMainThread) then + if not (ExecutingInMainThread) then Exit(Trampoline_user32_GetSysColorBrush(nIndex)); { @@ -193,11 +178,11 @@ procedure Detour_SetStyle(Self: TObject; Style: TCustomStyleServices); I: Integer; LActiveStyle: TCustomStyleServices; begin - if not(ExecutingInMainThread) then - begin - Trampoline_SetStyle(Self, Style); - exit; - end; + if not (ExecutingInMainThread) then + begin + Trampoline_SetStyle(Self, Style); + exit; + end; LActiveStyle := TStyleManager.ActiveStyle; Trampoline_SetStyle(Self, Style); @@ -210,23 +195,22 @@ procedure Detour_SetStyle(Self: TObject; Style: TCustomStyleServices); end; //based on JvThemes.DrawThemedFrameControl -function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UINT): BOOL; stdcall; +function Detour_WinApi_DrawFrameControl(DC: hDC; Rect: PRect; uType, uState: UINT): BOOL; stdcall; const Mask = $00FF; var LRect: TRect; LDetails: TThemedElementDetails; CanDraw: Boolean; - LThemedButton: TThemedButton; LThemedComboBox: TThemedComboBox; LThemedScrollBar: TThemedScrollBar; begin - if not(ExecutingInMainThread) then + if not (ExecutingInMainThread) then Exit(Trampoline_user32_DrawFrameControl(DC, Rect, uType, uState)); Result := False; - CanDraw:= (not StyleServices.IsSystemStyle) and (TSysStyleManager.Enabled) and (Rect <> nil); + CanDraw := (not StyleServices.IsSystemStyle) and (TSysStyleManager.Enabled) and (Rect <> nil); if CanDraw then begin LRect := Rect^; @@ -240,14 +224,11 @@ function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UIN begin if uState and DFCS_INACTIVE <> 0 then LThemedButton := tbPushButtonDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedButton := tbPushButtonPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedButton := tbPushButtonHot - else - if uState and DFCS_MONO <> 0 then + else if uState and DFCS_MONO <> 0 then LThemedButton := tbPushButtonDefaulted else LThemedButton := tbPushButtonNormal; @@ -264,25 +245,20 @@ function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UIN begin if uState and DFCS_INACTIVE <> 0 then LThemedButton := tbCheckBoxCheckedDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedButton := tbCheckBoxCheckedPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedButton := tbCheckBoxCheckedHot else LThemedButton := tbCheckBoxCheckedNormal; end - else - if uState and DFCS_MONO <> 0 then + else if uState and DFCS_MONO <> 0 then begin if uState and DFCS_INACTIVE <> 0 then LThemedButton := tbCheckBoxMixedDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedButton := tbCheckBoxMixedPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedButton := tbCheckBoxMixedHot else LThemedButton := tbCheckBoxMixedNormal; @@ -291,11 +267,9 @@ function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UIN begin if uState and DFCS_INACTIVE <> 0 then LThemedButton := tbCheckBoxUncheckedDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedButton := tbCheckBoxUncheckedPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedButton := tbCheckBoxUncheckedHot else LThemedButton := tbCheckBoxUncheckedNormal; @@ -311,11 +285,9 @@ function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UIN begin if uState and DFCS_INACTIVE <> 0 then LThemedButton := tbRadioButtonCheckedDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedButton := tbRadioButtonCheckedPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedButton := tbRadioButtonCheckedHot else LThemedButton := tbRadioButtonCheckedNormal; @@ -324,11 +296,9 @@ function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UIN begin if uState and DFCS_INACTIVE <> 0 then LThemedButton := tbRadioButtonUncheckedDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedButton := tbRadioButtonUncheckedPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedButton := tbRadioButtonUncheckedHot else LThemedButton := tbRadioButtonUncheckedNormal; @@ -347,11 +317,9 @@ function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UIN begin if uState and DFCS_INACTIVE <> 0 then LThemedComboBox := tcDropDownButtonDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedComboBox := tcDropDownButtonPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedComboBox := tcDropDownButtonHot else LThemedComboBox := tcDropDownButtonNormal; @@ -366,11 +334,9 @@ function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UIN begin if uState and DFCS_INACTIVE <> 0 then LThemedScrollBar := tsArrowBtnUpDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedScrollBar := tsArrowBtnUpPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedScrollBar := tsArrowBtnUpHot else LThemedScrollBar := tsArrowBtnUpNormal; @@ -385,11 +351,9 @@ function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UIN begin if uState and DFCS_INACTIVE <> 0 then LThemedScrollBar := tsArrowBtnDownDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedScrollBar := tsArrowBtnDownPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedScrollBar := tsArrowBtnDownHot else LThemedScrollBar := tsArrowBtnDownNormal; @@ -404,11 +368,9 @@ function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UIN begin if uState and DFCS_INACTIVE <> 0 then LThemedScrollBar := tsArrowBtnLeftDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedScrollBar := tsArrowBtnLeftPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedScrollBar := tsArrowBtnLeftHot else LThemedScrollBar := tsArrowBtnLeftNormal; @@ -423,11 +385,9 @@ function Detour_WinApi_DrawFrameControl(DC: HDC; Rect: PRect; uType, uState: UIN begin if uState and DFCS_INACTIVE <> 0 then LThemedScrollBar := tsArrowBtnRightDisabled - else - if uState and DFCS_PUSHED <> 0 then + else if uState and DFCS_PUSHED <> 0 then LThemedScrollBar := tsArrowBtnRightPressed - else - if uState and DFCS_HOT <> 0 then + else if uState and DFCS_HOT <> 0 then LThemedScrollBar := tsArrowBtnRightHot else LThemedScrollBar := tsArrowBtnRightNormal; @@ -461,18 +421,15 @@ function Detour_LoadIconW(_hInstance: HINST; lpIconName: PWideChar): HICON; stdc LHandle: THandle; MustRelease: Boolean; - procedure DrawIcon(const ACode: Word); - begin + procedure DrawIcon(const ACode: Word); + begin //DestroyIcon(LHandle); - Result := FontAwesome.GetIcon(ACode, LIcon.Width, LIcon.Height, GetStyleHighLightColor, StyleServices.GetSystemColor(clBtnFace), 0); - MustRelease:=False; - end; + Result := FontAwesome.GetIcon(ACode, LIcon.Width, LIcon.Height, GetStyleHighLightColor, StyleServices.GetSystemColor(clBtnFace), 0); + MustRelease := False; + end; begin - if not(ExecutingInMainThread) or - StyleServices.IsSystemStyle or - not(TSysStyleManager.Enabled) or - not(TSysStyleManager.HookDialogIcons) then + if not (ExecutingInMainThread) or StyleServices.IsSystemStyle or not (TSysStyleManager.Enabled) or not (TSysStyleManager.HookDialogIcons) then Exit(Trampoline_user32_LoadIconW(_hInstance, lpIconName)); if {(_hInstance>0) and (_hInstance<>HInstance) and} IS_INTRESOURCE(lpIconName) then @@ -487,26 +444,37 @@ function Detour_LoadIconW(_hInstance: HINST; lpIconName: PWideChar): HICON; stdc //OutputDebugString(PChar('Detour_LoadIconW '+s+ ' Module Name '+GetModuleName(_hInstance)+' _hInstance '+IntToHex(_hInstance, 8) )); case NativeUInt(lpIconName) of - 78: DrawIcon(fa_shield); - 81: DrawIcon(fa_info_circle); - 84: DrawIcon(fa_warning); - 98: DrawIcon(fa_minus_circle); - 99: DrawIcon(fa_question_circle); + 78: + DrawIcon(fa_shield); + 81: + DrawIcon(fa_info_circle); + 84: + DrawIcon(fa_warning); + 98: + DrawIcon(fa_minus_circle); + 99: + DrawIcon(fa_question_circle); end; - if _hInstance=0 then - case NativeUInt(lpIconName) of - 32518: DrawIcon(fa_shield); - 32516: DrawIcon(fa_info_circle); - 32515: DrawIcon(fa_warning); - 32513: DrawIcon(fa_minus_circle); - 32514: DrawIcon(fa_question_circle); - 32517: DrawIcon(fa_windows); - end; - + if _hInstance = 0 then + case NativeUInt(lpIconName) of + 32518: + DrawIcon(fa_shield); + 32516: + DrawIcon(fa_info_circle); + 32515: + DrawIcon(fa_warning); + 32513: + DrawIcon(fa_minus_circle); + 32514: + DrawIcon(fa_question_circle); + 32517: + DrawIcon(fa_windows); + end; + finally if MustRelease then - LIcon.ReleaseHandle; + LIcon.ReleaseHandle; LIcon.Free; end; end @@ -515,7 +483,7 @@ function Detour_LoadIconW(_hInstance: HINST; lpIconName: PWideChar): HICON; stdc end; {$IFDEF HOOK_UXTHEME} -function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, Y: Integer; Flags: UINT): THandle; stdcall; +function Detour_LoadImageW(hInst: hInst; ImageName: LPCWSTR; ImageType: UINT; X, Y: Integer; Flags: UINT): THandle; stdcall; const ExplorerFrame = 'explorerframe.dll'; var @@ -525,7 +493,7 @@ function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, LRect, LRect2: TRect; LBackColor, LColor: TColor; begin - if not(ExecutingInMainThread) or StyleServices.IsSystemStyle or not(TSysStyleManager.Enabled) then + if not (ExecutingInMainThread) or StyleServices.IsSystemStyle or not (TSysStyleManager.Enabled) then Exit(Trampoline_user32_LoadImageW(hInst, ImageName, ImageType, X, Y, Flags)); //w8 - W10 @@ -555,14 +523,13 @@ function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, end; Exit(Trampoline_user32_LoadImageW(hInst, ImageName, ImageType, X, Y, Flags)); end - else - if (hInst > 0) and (ImageType = IMAGE_BITMAP) and (X = 0) and (Y = 0) and IS_INTRESOURCE(ImageName) then + else if (hInst > 0) and (ImageType = IMAGE_BITMAP) and (X = 0) and (Y = 0) and IS_INTRESOURCE(ImageName) then begin hModule := GetModuleHandle(ExplorerFrame); if (hModule = hInst) then begin s := IntToStr(NativeUInt(ImageName)); - Result := Trampoline_user32_LoadImageW(hInst, ImageName, ImageType, X, Y, Flags); + Result := Trampoline_user32_LoadImageW(hInst, ImageName, ImageType, X, Y, Flags); LBitmap := TBitmap.Create; try LBitmap.Handle := Result; @@ -572,86 +539,86 @@ function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, if TOSVersion.Check(6, 2) then begin LBackColor := StyleServices.GetSystemColor(clWindow); - LRect := Rect(0, 0, LBitmap.Width, LBitmap.Height ); + LRect := Rect(0, 0, LBitmap.Width, LBitmap.Height); case NativeUInt(ImageName) of // Right Arrow, cross button, refresh, down arrow 288: begin - LColor := StyleServices.GetSystemColor(clBtnText); - Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); + LColor := StyleServices.GetSystemColor(clBtnText); + Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); - LRect := Rect(0, 0, 16, 16); - FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_arrow_right, LRect, LColor); + LRect := Rect(0, 0, 16, 16); + FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_arrow_right, LRect, LColor); - OffsetRect(LRect, 16, 0); - FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_remove, LRect, LColor); + OffsetRect(LRect, 16, 0); + FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_remove, LRect, LColor); - OffsetRect(LRect, 16, 0); - LRect2 := LRect; - InflateRect(LRect2, -2, -2); - FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_refresh, LRect2, LColor); + OffsetRect(LRect, 16, 0); + LRect2 := LRect; + InflateRect(LRect2, -2, -2); + FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_refresh, LRect2, LColor); - OffsetRect(LRect, 16 + 2, 0); - LRect2 := LRect; - InflateRect(LRect2, -2, -2); - FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_caret_down, LRect2, LColor); + OffsetRect(LRect, 16 + 2, 0); + LRect2 := LRect; + InflateRect(LRect2, -2, -2); + FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_caret_down, LRect2, LColor); - Bitmap32_SetAlphaExceptColor(LBitmap, 255, LBackColor); - end; - else - begin - Bitmap32_Grayscale(LBitmap); - _ProcessBitmap32(LBitmap, StyleServices.GetSystemColor(clHighlight), _BlendBurn) + Bitmap32_SetAlphaExceptColor(LBitmap, 255, LBackColor); end; + else + begin + Bitmap32_Grayscale(LBitmap); + _ProcessBitmap32(LBitmap, StyleServices.GetSystemColor(clHighlight), _BlendBurn) + end; end; end else //Windows Vista - W7 - if (TOSVersion.Major = 6) and ((TOSVersion.Minor = 0) or (TOSVersion.Minor = 1)) then + if (TOSVersion.Major = 6) and ((TOSVersion.Minor = 0) or (TOSVersion.Minor = 1)) then begin LBackColor := StyleServices.GetSystemColor(clWindow); - LRect := Rect(0, 0, LBitmap.Width, LBitmap.Height ); + LRect := Rect(0, 0, LBitmap.Width, LBitmap.Height); case NativeUInt(ImageName) of //Magnifier - 34560..34562, // Aero Enabled - 34563..34568: // Classic Theme - begin - LColor := StyleServices.GetSystemColor(clHighlight); - Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); - FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_search, LRect, LColor); - Bitmap32_SetAlphaExceptColor(LBitmap, 255, LBackColor); + 34560..34562, // Aero Enabled + 34563..34568: // Classic Theme + begin + LColor := StyleServices.GetSystemColor(clHighlight); + Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); + FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_search, LRect, LColor); + Bitmap32_SetAlphaExceptColor(LBitmap, 255, LBackColor); //Bitmap32_SetAlphaByColor(LBitmap, 255, LColor); - end; + end; //cross button normal - 34569..34571, // Aero Enabled - 34572..34574: // Classic Theme - begin - LColor:= StyleServices.GetSystemColor(clWindowText); + 34569..34571, // Aero Enabled + 34572..34574: // Classic Theme + begin + LColor := StyleServices.GetSystemColor(clWindowText); Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_remove, LRect, LColor); Bitmap32_SetAlphaExceptColor(LBitmap, 255, LBackColor); - end; + end; //cross button hot - 34575..34577, // Aero Enabled - 34581..34583, // Aero Enabled - 34578..34580: // Classic Theme - begin - LColor := StyleServices.GetSystemColor(clHighlight); - Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); - FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_remove, LRect, LColor); - Bitmap32_SetAlphaExceptColor(LBitmap, 255, LBackColor); - end; + 34575..34577, // Aero Enabled + 34581..34583, // Aero Enabled + 34578..34580: // Classic Theme + begin + LColor := StyleServices.GetSystemColor(clHighlight); + Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); + FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_remove, LRect, LColor); + Bitmap32_SetAlphaExceptColor(LBitmap, 255, LBackColor); + end; // Aero Enabled // Right Arrow, cross button, refresh, down arrow - 288: + 288: begin LColor := StyleServices.GetSystemColor(clBtnText); Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); - LRect:=Rect(0, 0, 16, 16); + LRect := Rect(0, 0, 16, 16); FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_arrow_right, LRect, LColor); OffsetRect(LRect, 16, 0); @@ -668,12 +635,12 @@ function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, // Classic Theme // Right Arrow, cross button, refresh, down arrow - 289, 290: + 289, 290: begin LColor := StyleServices.GetSystemColor(clBtnText); Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); - LRect:=Rect(0, 0, 21, 21); + LRect := Rect(0, 0, 21, 21); FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_arrow_right, LRect, LColor); OffsetRect(LRect, 21, 0); @@ -690,20 +657,23 @@ function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, // Aero Enabled // navigation buttons (arrows) - 577..579, - 581: + 577..579, 581: begin case NativeUInt(ImageName) of - 577: LColor := StyleServices.GetSystemColor(clBtnText); - 578: LColor := StyleServices.GetSystemColor(clHighlight); - 579: LColor := StyleServices.GetSystemColor(clGrayText); - 581: LColor := StyleServices.GetSystemColor(clBtnText); - else - LColor:= StyleServices.GetSystemColor(clBtnText); + 577: + LColor := StyleServices.GetSystemColor(clBtnText); + 578: + LColor := StyleServices.GetSystemColor(clHighlight); + 579: + LColor := StyleServices.GetSystemColor(clGrayText); + 581: + LColor := StyleServices.GetSystemColor(clBtnText); + else + LColor := StyleServices.GetSystemColor(clBtnText); end; Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); - + LRect := Rect(0, 0, 27, 27); InflateRect(LRect, -4, -4); FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_arrow_left, LRect, LColor); @@ -715,12 +685,15 @@ function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, //Classic Theme // navigation buttons (arrows) - 582..584: + 582..584: begin case NativeUInt(ImageName) of - 582: LColor := StyleServices.GetSystemColor(clBtnText); - 583: LColor := StyleServices.GetSystemColor(clHighlight); - 584: LColor := StyleServices.GetSystemColor(clGrayText); + 582: + LColor := StyleServices.GetSystemColor(clBtnText); + 583: + LColor := StyleServices.GetSystemColor(clHighlight); + 584: + LColor := StyleServices.GetSystemColor(clGrayText); else LColor := StyleServices.GetSystemColor(clBtnText); end; @@ -740,13 +713,12 @@ function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, LRect := Rect(60, 8, 72, 20); FontAwesome.DrawChar(LBitmap.Canvas.Handle, fa_caret_down, LRect, LColor); - Bitmap32_SetAlphaExceptColor(LBitmap, 255, LBackColor); end; //Aero Enabled //background navigation buttons - 280: + 280: begin Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); @@ -772,7 +744,7 @@ function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, //Classic Theme //background navigation buttons - 281: + 281: begin Bitmap32_SetAlphaAndColor(LBitmap, 1, LBackColor); end; @@ -794,19 +766,20 @@ function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, {$IFDEF HOOK_TDateTimePicker} {$IF CompilerVersion>=29} - function Detour_SetWindowTheme(hwnd: HWND; pszSubAppName: LPCWSTR; pszSubIdList: LPCWSTR): HRESULT; stdcall; - var - LControl: TWinControl; - begin - if not(ExecutingInMainThread) then - Exit(Trampoline_SetWindowTheme(hwnd, pszSubAppName, pszSubIdList)); - - LControl:= FindControl(hwnd); - if (pszSubAppName = '') and (pszSubIdList = '') and TStyleManager.IsCustomStyleActive and (LControl<>nil) and (LControl is TMonthCalendar) then - Exit(S_OK) - else - Exit(Trampoline_SetWindowTheme(hwnd, pszSubAppName, pszSubIdList)); - end; + +function Detour_SetWindowTheme(hwnd: hwnd; pszSubAppName: LPCWSTR; pszSubIdList: LPCWSTR): HRESULT; stdcall; +var + LControl: TWinControl; +begin + if not (ExecutingInMainThread) then + Exit(Trampoline_SetWindowTheme(hwnd, pszSubAppName, pszSubIdList)); + + LControl := FindControl(hwnd); + if (pszSubAppName = '') and (pszSubIdList = '') and TStyleManager.IsCustomStyleActive and (LControl <> nil) and (LControl is TMonthCalendar) then + Exit(S_OK) + else + Exit(Trampoline_SetWindowTheme(hwnd, pszSubAppName, pszSubIdList)); +end; {$IFEND CompilerVersion} {$ENDIF HOOK_TDateTimePicker} @@ -817,6 +790,7 @@ function Detour_LoadImageW(hInst: HINST; ImageName: LPCWSTR; ImageType: UINT; X, { TListStyleBrush } //Delete brushes + procedure TListStyleBrush.ValueNotify(const Value: HBRUSH; Action: TCollectionNotification); begin inherited; @@ -831,7 +805,6 @@ procedure TListStyleBrush.ValueNotify(const Value: HBRUSH; Action: TCollectionNo hnd: THandle; initialization - VCLStylesLock := TCriticalSection.Create; VCLStylesBrush := TObjectDictionary.Create([doOwnsValues]); @@ -842,7 +815,6 @@ initialization TCustomStyleEngine.RegisterStyleHook(TDateTimePicker, TStyleHook); {$ENDIF HOOK_TDateTimePicker} - {$IFDEF HOOK_TProgressBar} TCustomStyleEngine.RegisterStyleHook(TProgressBar, TStyleHook); {$ENDIF HOOK_TProgressBar} @@ -853,11 +825,11 @@ initialization @Trampoline_user32_GetSysColorBrush := InterceptCreate(user32, 'GetSysColorBrush', @Detour_GetSysColorBrush); @Trampoline_user32_FillRect := InterceptCreate(user32, 'FillRect', @Detour_FillRect); @Trampoline_user32_DrawEdge := InterceptCreate(user32, 'DrawEdge', @Detour_DrawEdge); - @Trampoline_user32_DrawFrameControl := InterceptCreate(user32, 'DrawFrameControl', @Detour_WinApi_DrawFrameControl); + @Trampoline_user32_DrawFrameControl := InterceptCreate(user32, 'DrawFrameControl', @Detour_WinApi_DrawFrameControl); @Trampoline_user32_LoadIconW := InterceptCreate(user32, 'LoadIconW', @Detour_LoadIconW); {$IFDEF HOOK_UXTHEME} if TOSVersion.Check(6) then - @Trampoline_user32_LoadImageW := InterceptCreate(user32, 'LoadImageW', @Detour_LoadImageW); + @Trampoline_user32_LoadImageW := InterceptCreate(user32, 'LoadImageW', @Detour_LoadImageW); {$ENDIF HOOK_UXTHEME} @@ -873,8 +845,8 @@ initialization EndTransaction(hnd); end; -finalization +finalization hnd := BeginTransaction(); InterceptRemove(@Trampoline_user32_GetSysColor); InterceptRemove(@Trampoline_user32_GetSysColorBrush); @@ -901,3 +873,4 @@ finalization VCLStylesLock := nil; end. + diff --git a/source/vcl-styles-utils/Vcl.Styles.Preview.pas b/source/vcl-styles-utils/Vcl.Styles.Preview.pas index 6fc8c4412..afa3e0985 100644 --- a/source/vcl-styles-utils/Vcl.Styles.Preview.pas +++ b/source/vcl-styles-utils/Vcl.Styles.Preview.pas @@ -159,9 +159,9 @@ function TVisualStylePreview.GetCaptionHeight: integer; begin LDetails := FStyle.GetElementDetails(twCaptionActive); if Assigned(Application.Mainform) then - FStyle.GetElementSize(0, LDetails, esActual, LSize, Application.MainForm.Monitor.PixelsPerInch) + FStyle.GetElementSize(0, LDetails, esActual, LSize{$IF (CompilerVersion >=33)}, Application.MainForm.Monitor.PixelsPerInch{$IFEND}) else - FStyle.GetElementSize(0, LDetails, esActual, LSize, Screen.PixelsPerInch); + FStyle.GetElementSize(0, LDetails, esActual, LSize{$IF (CompilerVersion >=33)}, Screen.PixelsPerInch{$IFEND}); Result := LSize.cy; end; @@ -172,9 +172,9 @@ function TVisualStylePreview.GetLeftFormBorderWidth: integer; begin LDetails := FStyle.GetElementDetails(twFrameLeftActive); if Assigned(Application.Mainform) then - FStyle.GetElementSize(0, LDetails, esActual, LSize, Application.MainForm.Monitor.PixelsPerInch) + FStyle.GetElementSize(0, LDetails, esActual, LSize{$IF (CompilerVersion >=33)}, Application.MainForm.Monitor.PixelsPerInch{$IFEND}) else - FStyle.GetElementSize(0, LDetails, esActual, LSize, Screen.PixelsPerInch); + FStyle.GetElementSize(0, LDetails, esActual, LSize{$IF (CompilerVersion >=33)}, Screen.PixelsPerInch{$IFEND}); Result := LSize.cx; end; @@ -185,9 +185,9 @@ function TVisualStylePreview.GetRightFormBorderWidth: integer; begin LDetails := FStyle.GetElementDetails(twFrameRightActive); if Assigned(Application.Mainform) then - FStyle.GetElementSize(0, LDetails, esActual, LSize, Application.MainForm.Monitor.PixelsPerInch) + FStyle.GetElementSize(0, LDetails, esActual, LSize{$IF (CompilerVersion >=33)}, Application.MainForm.Monitor.PixelsPerInch{$IFEND}) else - FStyle.GetElementSize(0, LDetails, esActual, LSize, Screen.PixelsPerInch); + FStyle.GetElementSize(0, LDetails, esActual, LSize{$IF (CompilerVersion >=33)}, Screen.PixelsPerInch{$IFEND}); Result := LSize.cx; end; @@ -198,9 +198,9 @@ function TVisualStylePreview.GetBottomFormBorderHeight: integer; begin LDetails := FStyle.GetElementDetails(twFrameBottomActive); if Assigned(Application.Mainform) then - FStyle.GetElementSize(0, LDetails, esActual, LSize, Application.MainForm.Monitor.PixelsPerInch) + FStyle.GetElementSize(0, LDetails, esActual, LSize{$IF (CompilerVersion >=33)}, Application.MainForm.Monitor.PixelsPerInch{$IFEND}) else - FStyle.GetElementSize(0, LDetails, esActual, LSize, Screen.PixelsPerInch); + FStyle.GetElementSize(0, LDetails, esActual, LSize{$IF (CompilerVersion >=33)}, Screen.PixelsPerInch{$IFEND}); Result := LSize.cy; end; diff --git a/source/vcl-styles-utils/Vcl.Styles.Utils.ComCtrls.pas b/source/vcl-styles-utils/Vcl.Styles.Utils.ComCtrls.pas index eb4a973c3..6ab8d6d7c 100644 --- a/source/vcl-styles-utils/Vcl.Styles.Utils.ComCtrls.pas +++ b/source/vcl-styles-utils/Vcl.Styles.Utils.ComCtrls.pas @@ -1226,21 +1226,21 @@ function TSysToolbarStyleHook.GetCount: Integer; procedure TSysToolbarStyleHook.ApplyImageList; var - H: Cardinal; + H: LRESULT; begin H := SendMessage(Handle, TB_GETIMAGELIST, 0, 0); if (H <> 0) and (FImages = nil) then begin FImages := TImageList.Create(nil); FImages.ShareImages := True; - FImages.Handle := H; + FImages.Handle := THandle(H); end; H := SendMessage(Handle, TB_GETDISABLEDIMAGELIST, 0, 0); if (H <> 0) and (FDisabledImages = nil) then begin FDisabledImages := TImageList.Create(nil); FDisabledImages.ShareImages := True; - FDisabledImages.Handle := H; + FDisabledImages.Handle := THandle(H); end; end; @@ -1826,7 +1826,9 @@ procedure TSysRichEditStyleHook.EMSetBkgndColor(var Message: TMessage); function TSysRichEditStyleHook.GetBorderSize: TRect; begin if SysControl.HasBorder then - Result := Rect(2, 2, 2, 2); + Result := Rect(2, 2, 2, 2) + else + Result := Rect(0, 0, 0, 0); end; procedure TSysRichEditStyleHook.UpdateColors; diff --git a/source/vcl-styles-utils/Vcl.Styles.Utils.Menus.pas b/source/vcl-styles-utils/Vcl.Styles.Utils.Menus.pas index a92c2fa4b..94ca4a8a3 100644 --- a/source/vcl-styles-utils/Vcl.Styles.Utils.Menus.pas +++ b/source/vcl-styles-utils/Vcl.Styles.Utils.Menus.pas @@ -415,6 +415,7 @@ procedure TSysPopupStyleHook.DrawItem(Canvas: TCanvas; const Index: integer; con var LTextRect: TRect; DC: HDC; + LPixelsPerInch: Integer; procedure DrawSubMenu(const ItemRect: TRect); var @@ -429,7 +430,7 @@ procedure TSysPopupStyleHook.DrawItem(Canvas: TCanvas; const Index: integer; con if isDisabled in State then LSubMenuDetail := tmPopupSubMenuDisabled; LSubMenuDetails := StyleServices.GetElementDetails(LSubMenuDetail); - StyleServices.GetElementSize(DC, LSubMenuDetails, esActual, SubMenuSize); + StyleServices.GetElementSize(DC, LSubMenuDetails, esActual, SubMenuSize, LPixelsPerInch); if not RightToLeft then LSubMenuRect := Rect(ItemRect.Right - SubMenuSize.cx, ItemRect.Top, ItemRect.Right, ItemRect.Top + SubMenuSize.cy) else @@ -439,7 +440,7 @@ procedure TSysPopupStyleHook.DrawItem(Canvas: TCanvas; const Index: integer; con LBitmap.SetSize(SubMenuSize.Width, SubMenuSize.Height); LBitmap.Canvas.Brush.Color := clFuchsia; LBitmap.Canvas.FillRect(Rect(0, 0, SubMenuSize.Width, SubMenuSize.Height)); - StyleServices.DrawElement(LBitmap.Canvas.Handle, LSubMenuDetails, Rect(0, 0, SubMenuSize.Width, SubMenuSize.Height)); + StyleServices.DrawElement(LBitmap.Canvas.Handle, LSubMenuDetails, Rect(0, 0, SubMenuSize.Width, SubMenuSize.Height), nil, LPixelsPerInch); if RightToLeft then begin RotateBitmap(LBitmap, DegToRad(180), False, clFuchsia); @@ -534,6 +535,10 @@ procedure TSysPopupStyleHook.DrawItem(Canvas: TCanvas; const Index: integer; con begin + if Assigned(Application.Mainform) then + LPixelsPerInch := Application.MainForm.Monitor.PixelsPerInch + else + LPixelsPerInch := screen.PixelsPerInch; DisplayCheckedGlyph := True; ItemRect2 := ItemRect; ItemRect2.Top := ItemRect.Top - GetOffset(True); //add offset diff --git a/source/vcl-styles-utils/Vcl.Styles.Utils.StdCtrls.pas b/source/vcl-styles-utils/Vcl.Styles.Utils.StdCtrls.pas index c5c0d3973..88a2bc37f 100644 --- a/source/vcl-styles-utils/Vcl.Styles.Utils.StdCtrls.pas +++ b/source/vcl-styles-utils/Vcl.Styles.Utils.StdCtrls.pas @@ -272,7 +272,9 @@ destructor TSysEditStyleHook.Destroy; function TSysEditStyleHook.GetBorderSize: TRect; begin if SysControl.HasBorder then - Result := Rect(2, 2, 2, 2); + Result := Rect(2, 2, 2, 2) + else + Result := Rect(0, 0, 0, 0); end; procedure TSysEditStyleHook.MouseEnter; @@ -997,7 +999,9 @@ constructor TSysMemoStyleHook.Create(AHandle: THandle); function TSysMemoStyleHook.GetBorderSize: TRect; begin if SysControl.HasBorder then - Result := Rect(2, 2, 2, 2); + Result := Rect(2, 2, 2, 2) + else + Result := Rect(0, 0, 0, 0); end; procedure TSysMemoStyleHook.UpdateColors; diff --git a/source/vcl-styles-utils/Vcl.Styles.Utils.SystemMenu.pas b/source/vcl-styles-utils/Vcl.Styles.Utils.SystemMenu.pas index e36b41df2..53f346d96 100644 --- a/source/vcl-styles-utils/Vcl.Styles.Utils.SystemMenu.pas +++ b/source/vcl-styles-utils/Vcl.Styles.Utils.SystemMenu.pas @@ -210,9 +210,11 @@ procedure TVclStylesSystemMenu.CreateMenuStyles; CheckMenuItem(FVCLStylesMenu, LSubMenuIndex, MF_BYPOSITION or MF_CHECKED); if SameText('Windows', s) then + begin + inc(LSubMenuIndex); AddMenuSeparatorHelper(FVCLStylesMenu, LSubMenuIndex); + end; - inc(LSubMenuIndex); inc(uIDNewItem); LMethodInfo := TMethodInfo.Create; LMethodInfo.Value1 := s; diff --git a/source/view.pas b/source/view.pas index 1b364c17f..a30ff75b8 100644 --- a/source/view.pas +++ b/source/view.pas @@ -250,7 +250,7 @@ function TfrmView.ComposeCreateStatement: TSQLBatch; sql := sql + 'RENAME TABLE '+ViewName + ' TO '+RenameView + ';' + sLineBreak; end; - Result := TSQLBatch.Create; + Result := TSQLBatch.Create(DBObject.Connection.Parameters.NetTypeGroup); Result.SQL := Trim(SQL); end;